cmake_minimum_required(VERSION 3.12...4.0)

project(zuma-portable LANGUAGES C CXX)

# The decompiled game uses this separate macro for debug hotkeys. A CMake
# Release build must not expose level completion, extra lives or unlock cheats.
set_property(SOURCE src/Zuma/Board.cpp src/Zuma/AdventureScreen.cpp src/Zuma/PracticeScreen.cpp
 APPEND PROPERTY COMPILE_DEFINITIONS "$<$<CONFIG:Release>:RELEASEFINAL>")

include(CMake/ProjectVersion.cmake)

option(BUILD_STATIC "Statically link dependencies for a standalone exe (Windows MinGW, Linux)" OFF)
if(BUILD_STATIC)
	if(WIN32 AND NOT MSVC)
		set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
		set(BUILD_SHARED_LIBS OFF)
		add_link_options(-static)
	elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
		set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
		set(BUILD_SHARED_LIBS OFF)
	elseif(MSVC)
		message(WARNING "BUILD_STATIC is not supported for MSVC. Use a vcpkg -static triplet instead.")
		set(BUILD_STATIC OFF)
	else()
		message(WARNING "BUILD_STATIC is only supported on Windows with MinGW-based toolchains and on Linux.")
		set(BUILD_STATIC OFF)
	endif()
endif()

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
if (CMAKE_GENERATOR MATCHES "Ninja")
	set(CMAKE_CXX_SCAN_FOR_MODULES OFF)
endif()

if(MSVC)
	if(DEFINED VCPKG_TARGET_TRIPLET AND VCPKG_TARGET_TRIPLET MATCHES "-static")
		set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
	elseif(DEFINED VCPKG_TARGET_TRIPLET)
		set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
	endif()
endif()

file(GLOB SOURCES CONFIGURE_DEPENDS
	src/*.cpp
	src/SexyAppFramework/*.cpp
	src/SexyAppFramework/graphics/*.cpp
	src/SexyAppFramework/imagelib/*.cpp
	src/SexyAppFramework/misc/*.cpp
	src/SexyAppFramework/paklib/*.cpp
	src/SexyAppFramework/sound/*.cpp
	src/SexyAppFramework/widget/*.cpp
	src/SexyAppFramework/fcaseopen/*.c
	src/Zuma/*.cpp
)

option(LOW_MEMORY "Enable low memory mode" OFF)
if(NINTENDO_SWITCH)
	list(APPEND SOURCES
		${CMAKE_CURRENT_SOURCE_DIR}/src/SexyAppFramework/platform/switch/Window.cpp
		${CMAKE_CURRENT_SOURCE_DIR}/src/SexyAppFramework/platform/switch/Input.cpp
	)
	list(APPEND PLAT_INCLUDES
		${CMAKE_CURRENT_SOURCE_DIR}/src/SexyAppFramework/platform/switch
	)
elseif(EMSCRIPTEN)
	# libopenmpt has no Emscripten port; must be pre-built with emmake.
	find_library(OPENMPT_LIBRARY NAMES openmpt libopenmpt REQUIRED)
	set(OPENMPT_LIB ${OPENMPT_LIBRARY})

	list(APPEND SOURCES
		${CMAKE_CURRENT_SOURCE_DIR}/src/SexyAppFramework/platform/emscripten/Window.cpp
		${CMAKE_CURRENT_SOURCE_DIR}/src/SexyAppFramework/platform/default/Input.cpp
	)
	list(APPEND PLAT_INCLUDES
		${CMAKE_CURRENT_SOURCE_DIR}/src/SexyAppFramework/platform/emscripten
	)
elseif(ANDROID)
	# Native dependency prefix built by scripts/build_android_deps.sh
	# (SDL2 + libpng + libjpeg-turbo for the target ABI). Override with the
	# ZUMA_ANDROID_DEPS environment variable.
	# NOTE: the NDK toolchain sets CMAKE_FIND_ROOT_PATH_MODE_{LIBRARY,INCLUDE}=ONLY,
	# so prebuilt deps must be added to CMAKE_FIND_ROOT_PATH or find_path/find_library
	# will never look outside the sysroot.
	if(DEFINED ENV{ZUMA_ANDROID_DEPS})
		list(APPEND CMAKE_PREFIX_PATH "$ENV{ZUMA_ANDROID_DEPS}")
		list(APPEND CMAKE_FIND_ROOT_PATH "$ENV{ZUMA_ANDROID_DEPS}")
	else()
		# Local builds: scripts/build-android-apk.sh installs the ABI dependency
		# set into android-deps/<abi> at the repository root.
		list(APPEND CMAKE_PREFIX_PATH "${CMAKE_CURRENT_SOURCE_DIR}/android-deps/${ANDROID_ABI}")
		list(APPEND CMAKE_FIND_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/android-deps/${ANDROID_ABI}")
	endif()

	# Port note: Zuma Deluxe's music is a MO3 tracker module, which needs
	# libopenmpt, and there is no Android build of it in this tree yet. Build
	# without music for now - the game runs, the soundtrack is silent. Adding
	# libopenmpt for Android is a follow-up milestone.
	set(OPENMPT_LIB "")

	# Android goes through SDL2 exactly like the desktop, so it uses the default
	# platform layer (SDLActivity drives SDL_main). Without these the framework's
	# window/input/text-input symbols are left undefined at link time.
	list(APPEND SOURCES
		${CMAKE_CURRENT_SOURCE_DIR}/src/SexyAppFramework/platform/default/Window.cpp
		${CMAKE_CURRENT_SOURCE_DIR}/src/SexyAppFramework/platform/default/Input.cpp
	)
	list(APPEND PLAT_INCLUDES
		${CMAKE_CURRENT_SOURCE_DIR}/src/SexyAppFramework/platform/default
	)
else()
	if(VCPKG_TOOLCHAIN)
		find_package(libopenmpt CONFIG REQUIRED)
		set(OPENMPT_LIB libopenmpt::libopenmpt)
	else()
		find_library(OPENMPT_LIBRARY NAMES openmpt libopenmpt REQUIRED)
		set(OPENMPT_LIB ${OPENMPT_LIBRARY})
		if(OPENMPT_LIBRARY MATCHES "\\${CMAKE_STATIC_LIBRARY_SUFFIX}$")
			find_path(OPENMPT_INCLUDE_DIR libopenmpt/libopenmpt.h REQUIRED)
			include(CheckCXXSourceCompiles)
			include(CMakePushCheckState)
			cmake_push_check_state()
			set(CMAKE_REQUIRED_INCLUDES ${OPENMPT_INCLUDE_DIR})
			set(CMAKE_REQUIRED_LIBRARIES ${OPENMPT_LIBRARY})
			check_cxx_source_compiles("#include <libopenmpt/libopenmpt.h>
int main() {
	return openmpt_module_create_from_memory2(0, 0, 0, 0, 0, 0, 0, 0, 0) != 0;
}" OPENMPT_LINKS_STANDALONE)
			cmake_pop_check_state()
			if(NOT OPENMPT_LINKS_STANDALONE)
				find_library(MPG123_LIBRARY NAMES mpg123 libmpg123)
				find_library(VORBISFILE_LIBRARY NAMES vorbisfile libvorbisfile)
				find_library(VORBIS_LIBRARY NAMES vorbis libvorbis)
				find_library(OGG_LIBRARY NAMES ogg libogg)
				if(NOT MPG123_LIBRARY OR NOT VORBISFILE_LIBRARY OR NOT VORBIS_LIBRARY OR NOT OGG_LIBRARY)
					message(FATAL_ERROR "Static libopenmpt does not link standalone and some of its dependencies were not found (see the configure log for details)")
				endif()
				list(APPEND OPENMPT_LIB ${MPG123_LIBRARY} ${VORBISFILE_LIBRARY} ${VORBIS_LIBRARY} ${OGG_LIBRARY})
			endif()
		endif()
	endif()
	list(APPEND SOURCES
		${CMAKE_CURRENT_SOURCE_DIR}/src/SexyAppFramework/platform/default/Window.cpp
		${CMAKE_CURRENT_SOURCE_DIR}/src/SexyAppFramework/platform/default/Input.cpp
	)
	list(APPEND PLAT_INCLUDES
		${CMAKE_CURRENT_SOURCE_DIR}/src/SexyAppFramework/platform/default
	)
endif()

if(CMAKE_BUILD_TYPE AND CMAKE_BUILD_TYPE MATCHES "^[Dd][Ee][Bb][Uu][Gg]$")
	set(WIN_CONSOLE_DEFAULT ON)
	set(PVZ_DEBUG_DEFAULT ON)
else()
	set(WIN_CONSOLE_DEFAULT OFF)
	set(PVZ_DEBUG_DEFAULT OFF)
endif()

option(PVZ_DEBUG "Enable PVZ_DEBUG macro to enable cheats, hidden features, etc." ${PVZ_DEBUG_DEFAULT})
option(CONSOLE "Show console on Windows" ${WIN_CONSOLE_DEFAULT})
option(DO_FIX_BUGS "Define DO_FIX_BUGS macro (Community fixes for original game bugs of 1.2.0.1073 GOTY Edition)" OFF)

if(EMSCRIPTEN)
	list(PREPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/CMake")
	set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -sUSE_SDL=2 -sUSE_ZLIB=1 -sUSE_LIBPNG=1 -sUSE_LIBJPEG=1 -sUSE_VORBIS=1")
	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -sUSE_SDL=2 -sUSE_ZLIB=1 -sUSE_LIBPNG=1 -sUSE_LIBJPEG=1 -sUSE_VORBIS=1 -fexceptions")
else()
	find_package(Threads REQUIRED)
	find_package(ZLIB REQUIRED)
	find_package(JPEG REQUIRED)
	find_package(PNG REQUIRED)
	find_package(SDL2 REQUIRED)
endif()

function(_pvz_patch_sdl2_iface_libs target)
	get_target_property(_libs ${target} INTERFACE_LINK_LIBRARIES)
	if(NOT _libs)
		return()
	endif()
	set(_patched)
	foreach(_lib IN LISTS _libs)
		if(_lib STREQUAL "SDL2::SDL2")
			list(APPEND _patched SDL2::SDL2-static)
		else()
			list(APPEND _patched "${_lib}")
		endif()
	endforeach()
	set_target_properties(${target} PROPERTIES INTERFACE_LINK_LIBRARIES "${_patched}")
endfunction()

if(BUILD_STATIC AND TARGET SDL2::SDL2-static)
	if(TARGET SDL2::SDL2main)
		_pvz_patch_sdl2_iface_libs(SDL2::SDL2main)
	endif()
	set(SDL2_LINK_TARGET SDL2::SDL2-static)
else()
	set(SDL2_LINK_TARGET SDL2::SDL2)
endif()

set(SDL_MIXER_X_STATIC ON CACHE BOOL "Build static library of SDL Mixer X")
set(SDL_MIXER_X_SHARED OFF CACHE BOOL "Build shared library of SDL Mixer X")
set(USE_MIDI OFF CACHE BOOL "SDL Mixer X midi")
set(USE_MODPLUG OFF CACHE BOOL "SDL Mixer X modplug")
set(USE_XMP OFF CACHE BOOL "SDL Mixer X xmp")
set(USE_OPUS OFF CACHE BOOL "SDL Mixer X opus")
set(USE_DRFLAC OFF CACHE BOOL "SDL Mixer X flac")
set(USE_PXTONE OFF CACHE BOOL "SDL Mixer X pxtone")
# Force the bundled OGG/MP3 decoders (stb_vorbis + dr_mp3) so the build never
# depends on external libvorbis/libogg/libmpg123 (not in vcpkg.json). The vcpkg
# toolchain used by CI can otherwise flip SDL-Mixer-X onto the system-libvorbis
# and mpg123 paths, which then fail to link libmain.so with undefined ov_*/mpg123_*.
set(USE_OGG_VORBIS     ON  CACHE BOOL "SDL Mixer X OGG Vorbis codec")
set(USE_OGG_VORBIS_STB ON  CACHE BOOL "SDL Mixer X: use bundled stb_vorbis (no external libvorbis)")
set(USE_MP3_DRMP3      ON  CACHE BOOL "SDL Mixer X: use bundled dr_mp3")
set(USE_MP3_MPG123     OFF CACHE BOOL "SDL Mixer X: disable mpg123 (needs external libmpg123)")
add_subdirectory(src/SexyAppFramework/sound/SDL-Mixer-X)

# Patch SDL-Mixer-X's transitive SDL2 dep without touching vendored CMake files
if(BUILD_STATIC AND TARGET SDL2::SDL2-static)
	_pvz_patch_sdl2_iface_libs(SDL2_mixer_ext_Static)
endif()

# Android requires a shared library loaded by SDLActivity; other platforms use an executable.
if(ANDROID)
	add_library(zuma-portable SHARED ${SOURCES})
elseif(IOS)
	add_executable(zuma-portable MACOSX_BUNDLE ${SOURCES})
else()
	add_executable(zuma-portable ${SOURCES})
endif()

target_include_directories(zuma-portable PRIVATE
	${PLAT_INCLUDES}
	${PROJECT_BINARY_DIR}
	${PROJECT_SOURCE_DIR}/src
	${PROJECT_SOURCE_DIR}/src/SexyAppFramework
	${PROJECT_SOURCE_DIR}/src/SexyAppFramework/sound/SDL-Mixer-X/include
	${SDL2_INCLUDE_DIRS}
)
target_compile_definitions(zuma-portable PRIVATE
	$<$<BOOL:${PVZ_DEBUG}>:PVZ_DEBUG>
	$<$<BOOL:${DO_FIX_BUGS}>:DO_FIX_BUGS>
)
target_compile_features(zuma-portable PRIVATE cxx_std_20)
set_target_properties(zuma-portable PROPERTIES CXX_EXTENSIONS OFF)

if (LOW_MEMORY)
	target_compile_definitions(zuma-portable PRIVATE LOW_MEMORY)
endif()

if(EMSCRIPTEN)
	target_sources(zuma-portable PRIVATE core/launch_license.c core/launch_state.c core/launch_platform.c core/vendor/monocypher/monocypher.c core/vendor/monocypher/monocypher-ed25519.c)
    set_property(TARGET zuma-portable APPEND PROPERTY LINK_DEPENDS
		"${CMAKE_CURRENT_SOURCE_DIR}/wasm/shell.html"
	)
	target_link_libraries(zuma-portable PRIVATE
		SDL2_mixer_ext_Static
		${OPENMPT_LIB}
	)
	target_link_options(zuma-portable PRIVATE
		"SHELL:-sUSE_SDL=2"
		"SHELL:-sUSE_ZLIB=1"
		"SHELL:-sUSE_LIBPNG=1"
		"SHELL:-sUSE_LIBJPEG=1 -sUSE_VORBIS=1"
		"SHELL:-fexceptions"
		"SHELL:-sASYNCIFY"
		"SHELL:-sALLOW_MEMORY_GROWTH=1"
		"SHELL:-sMAXIMUM_MEMORY=1073741824"
		"SHELL:-sGROWABLE_ARRAYBUFFERS=0"
		"SHELL:-sFORCE_FILESYSTEM=1"
		"SHELL:-sEXPORTED_RUNTIME_METHODS=[\"FS\",\"callMain\",\"HEAPU8\"]"
        "SHELL:-sEXPORTED_FUNCTIONS=[\"_main\",\"_malloc\",\"_free\",\"_zuma_launch_begin\",\"_zuma_launch_verify\",\"_zuma_launch_build\"]"
        "SHELL:-sMODULARIZE=1"
        "SHELL:-sEXPORT_NAME=createZuma"
        "SHELL:-sENVIRONMENT=web"
        "SHELL:-sINITIAL_MEMORY=67108864"
		"SHELL:-sINVOKE_RUN=0"
		"SHELL:-lidbfs.js"
		
	)
	set_target_properties(zuma-portable PROPERTIES SUFFIX ".js")
else()
	target_link_libraries(zuma-portable PRIVATE
		SDL2_mixer_ext_Static 
		${OPENMPT_LIB} 
		PNG::PNG
		JPEG::JPEG
		ZLIB::ZLIB
		SDL2::SDL2main
		${SDL2_LINK_TARGET}
		Threads::Threads
	)
endif()

if (WIN32)
	if(MSVC)
		target_compile_options(zuma-portable PRIVATE /utf-8)
	endif()

	target_sources(zuma-portable PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/SexyAppFramework/LawnProject.rc)

	set_source_files_properties(
		${CMAKE_CURRENT_SOURCE_DIR}/src/SexyAppFramework/LawnProject.rc
		PROPERTIES
			COMPILE_DEFINITIONS "APP_ICON_PATH=\"${CMAKE_CURRENT_SOURCE_DIR}/icon.ico\""
			OBJECT_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/icon.ico"
	)

	target_link_libraries(zuma-portable PRIVATE ws2_32 user32 gdi32 winmm imm32 shlwapi)
	target_compile_definitions(zuma-portable PRIVATE WINDOWS)

	if(NOT CONSOLE)
		if(MSVC)
			set_target_properties(zuma-portable PROPERTIES WIN32_EXECUTABLE TRUE)
		else()
			target_link_options(zuma-portable PRIVATE -mwindows)
		endif()
	endif()
elseif (NINTENDO_SWITCH)
	target_link_libraries(zuma-portable PRIVATE GLESv2 EGL glapi drm_nouveau)

	nx_generate_nacp(zuma-portable.nacp
		NAME "PvZ Portable"
		AUTHOR "wszqkzqk, etc., a community-driven port"
		VERSION "${PVZP_VERSION_PLAIN}"
	)

	nx_create_nro(zuma-portable
		NACP zuma-portable.nacp
		ICON ${CMAKE_CURRENT_SOURCE_DIR}/icon-switch.jpg
	)
elseif (ANDROID)
	target_link_libraries(zuma-portable PRIVATE GLESv2 EGL android log)
	set_target_properties(zuma-portable PROPERTIES OUTPUT_NAME "main")
elseif (IOS)
	target_link_libraries(zuma-portable PRIVATE
		"-framework Foundation"
		"-framework UIKit"
		"-framework OpenGLES"
		"-framework QuartzCore"
		"-framework CoreGraphics"
		"-framework CoreMotion"
		"-framework GameController"
		"-framework AVFoundation"
		"-framework AudioToolbox"
		"-framework Metal"
	)

	configure_file(ios/Info.plist.in "${CMAKE_CURRENT_BINARY_DIR}/Info.plist" @ONLY)
	set_target_properties(zuma-portable PROPERTIES
		MACOSX_BUNDLE TRUE
		MACOSX_BUNDLE_GUI_IDENTIFIER "io.github.wszqkzqk.zuma-portable"
		MACOSX_BUNDLE_BUNDLE_NAME "PvZ Portable"
		MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_BINARY_DIR}/Info.plist"
		XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
		XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET "16.4"
		XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "io.github.wszqkzqk.zuma-portable"
		XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "-"
		XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
	)

	# Add launch storyboard to the bundle
	if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/ios/LaunchScreen.storyboard")
		target_sources(zuma-portable PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/ios/LaunchScreen.storyboard")
		set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/ios/LaunchScreen.storyboard"
			PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
	endif()

	# Add asset catalog (app icon)
	if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/ios/Assets.xcassets")
		target_sources(zuma-portable PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/ios/Assets.xcassets")
		set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/ios/Assets.xcassets"
			PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
	endif()
endif()
