cmake_minimum_required(VERSION 3.13)
project(StratoHAL LANGUAGES C CXX)

#
# Configuration
#

# Target
option(STRATO_TARGET_WINDOWS          "Build for Windows"                  OFF)
option(STRATO_TARGET_MACOS            "Build for macOS"                    OFF)
option(STRATO_TARGET_LINUX            "Build for Linux X11"                OFF)
option(STRATO_TARGET_LINUX_WAYLAND    "Build for Linux Wayland"            OFF)
option(STRATO_TARGET_LINUX_GBM        "Build for Linux Framebuffer"        OFF)
option(STRATO_TARGET_IOS              "Build for iOS"                      OFF)
option(STRATO_TARGET_ANDROID          "Build for Android"                  OFF)
option(STRATO_TARGET_WASM             "Build for Emscripten"               OFF)
option(STRATO_TARGET_WASM_LOCAL       "Build for Emscripten (local file)"  OFF)
option(STRATO_TARGET_UNITY            "Build for Unity"                    OFF)
option(STRATO_TARGET_FREEBSD          "Build for FreeBSD X11"              OFF)
option(STRATO_TARGET_FREEBSD_WAYLAND  "Build for FreeBSD Wayland"          OFF)
option(STRATO_TARGET_NETBSD           "Build for NetBSD"                   OFF)
option(STRATO_TARGET_OPENBSD          "Build for OpenBSD"                  OFF)

# Options
option(STRATO_ENABLE_I18N          "Enable translation"                 OFF)
option(STRATO_ENABLE_OBJECT        "Build an object library"            OFF)
option(STRATO_ENABLE_SHARED        "Use shared libraries"               OFF)
option(STRATO_ENABLE_ROT90         "Rotate screen"                      OFF)

#
# Automatic Target Detection
#

# For when a target is manually specified.
if(   STRATO_TARGET_WINDOWS
   OR STRATO_TARGET_MACOS
   OR STRATO_TARGET_LINUX
   OR STRATO_TARGET_LINUX_WAYLAND
   OR STRATO_TARGET_LINUX_GBM
   OR STRATO_TARGET_IOS
   OR STRATO_TARGET_ANDROID
   OR STRATO_TARGET_WASM
   OR STRATO_TARGET_WASM_LOCAL
   OR STRATO_TARGET_UNITY
   OR STRATO_TARGET_FREEBSD
   OR STRATO_TARGET_FREEBSD_WAYLAND
   OR STRATO_TARGET_NETBSD
   OR STRATO_TARGET_OPENBSD
)
  # A target and options are manually specified.
else()   
  if(WIN32)
    set(STRATO_TARGET_WINDOWS    ON)
  endif()
  if(APPLE AND NOT IOS)
    set(STRATO_TARGET_MACOS      ON)
  endif()
  if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
    set(STRATO_TARGET_LINUX      ON)
  endif()
  if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
    set(STRATO_TARGET_FREEBSD    ON)
  endif()
  if(CMAKE_SYSTEM_NAME STREQUAL "NetBSD")
    set(STRATO_TARGET_NETBSD     ON)
  endif()
  if(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
    set(STRATO_TARGET_OPENBSD    ON)
  endif()
  # No automatic detection for:
  #  - iOS
  #  - Android
  #  - Wasm (Emscripten)
  #  - Unity
endif()

#
# Build Type
#

# Use "Release" build type by default.
if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type (Debug or Release)" FORCE)
endif()

# Debug Configuration
if(MSVC)
    set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Zi /DDEBUG /UNDEBUG")
    set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /Zi /DDEBUG /UNDEBUG")
else()
    set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_RELEASE} -O0 -g3 -UNDEBUG")
    set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_RELEASE} -O0 -g3 -UNDEBUG")
endif()

# Release Configuration
if(MSVC)
  # MSVC
  set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /O2 /DNDEBUG")
  set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2 /DNDEBUG")
elseif(CMAKE_C_COMPILER_ID MATCHES "Clang" OR CMAKE_C_COMPILER_ID STREQUAL "GNU")
  # GCC/Clang
  set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2 -g0 -DNDEBUG")
  set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -g0 -DNDEBUG")
endif()

#
# Dependency
#

if(STRATO_ENABLE_SHARED)
  # Use system installations.
  list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
  find_package(PNG REQUIRED)
  find_package(JPEG REQUIRED)
  find_package(WebP REQUIRED)
  find_package(Ogg REQUIRED)
  find_package(Vorbis MODULE REQUIRED)
  find_package(Freetype REQUIRED)
  find_package(Brotli REQUIRED)
  find_package(BZip2 REQUIRED)
  find_package(ZLIB REQUIRED)
else()
  # Build the dependencies.
  include(cmake/zlib.cmake)
  include(cmake/libpng.cmake)
  include(cmake/jpeg.cmake)
  include(cmake/libwebp.cmake)
  include(cmake/libogg.cmake)
  include(cmake/libvorbis.cmake)
  include(cmake/brotli.cmake)
  include(cmake/bzip2.cmake)
  include(cmake/freetype.cmake)
endif()

if(   STRATO_TARGET_LINUX
   OR STRATO_TARGET_FREEBSD
   OR STRATO_TARGET_NETBSD
   OR STRATO_TARGET_OPENBSD)
  find_library(X11_LIBRARY X11 REQUIRED)
  find_library(XPM_LIBRARY Xpm REQUIRED)
  find_library(GL_LIBRARY GL REQUIRED)
  if(STRATO_TARGET_LINUX OR STRATO_TARGET_FREEBSD)
    find_library(GLX_LIBRARY GLX)
  endif()
endif()

if(STRATO_TARGET_LINUX)
  find_library(ALSA_LIBRARY asound REQUIRED)
endif()

if(STRATO_TARGET_LINUX_GBM)
  find_library(GBM_LIBRARY gbm REQUIRED)
  find_library(DRM_LIBRARY drm REQUIRED)
endif()

if(   STRATO_TARGET_LINUX_WAYLAND
   OR STRATO_TARGET_FREEBSD_WAYLAND)
  find_package(PkgConfig REQUIRED)
  pkg_check_modules(WAYLAND REQUIRED wayland-client wayland-egl)
  pkg_check_modules(EGL     REQUIRED egl)
  pkg_check_modules(GLES2   REQUIRED glesv2)
endif()

#
# Checks
#

include(CheckIncludeFile)

check_include_file("stdint.h" HAVE_STDINT_H)
if(HAVE_STDINT_H)
  add_definitions(-DHAVE_STDINT_H=1)
endif()

check_include_file("inttypes.h" HAVE_INTTYPES_H)
if(HAVE_INTTYPES_H)
  add_definitions(-DHAVE_INTTYPES_H=1)
endif()

check_include_file("sys/types.h" HAVE_SYS_TYPES_H)
if(HAVE_SYS_TYPES_H)
  add_definitions(-DHAVE_SYS_TYPES_H=1)
endif()

#
# Main Target
#

# For Windows.
if(STRATO_TARGET_WINDOWS)
  set(STRATO_SOURCES
    src/image.c
    src/glyph.c
    src/wave.c
    src/stdfile.c
    src/winmain.c
    src/d3drender.c
    src/d3d12render.cc
    src/d3d11render.cc
    src/d3d9render.cc
    src/gdirender.c
    src/dsound.c
    src/dsvideo.cc
    src/digamepad.cc
    src/xigamepad.c
  )
endif()

# For macOS.
if(STRATO_TARGET_MACOS)
  set(STRATO_SOURCES
    src/image.c
    src/glyph.c
    src/wave.c
    src/stdfile.c
    src/nsmain.m
    src/aunit.c
    src/GameRenderer.m
    src/GameShaders.c
  )
endif()

# For Linux and *BSD. (X11)
if(   STRATO_TARGET_LINUX
   OR STRATO_TARGET_FREEBSD
   OR STRATO_TARGET_NETBSD
   OR STRATO_TARGET_OPENBSD
)
  set(STRATO_SOURCES
      src/image.c
      src/glyph.c
      src/wave.c
      src/stdfile.c
      src/x11main.c
      src/icon.c
      src/glrender.c
      src/asound.c
      src/bsdsound.c
      src/nosound.c
      src/evgamepad.c
      src/gstplay.c
    )
endif()

# For Linux Wayland.
if(   STRATO_TARGET_LINUX_WAYLAND
   OR STRATO_TARGET_FREEBSD_WAYLAND)
  set(STRATO_SOURCES
      src/image.c
      src/glyph.c
      src/wave.c
      src/stdfile.c
      src/wlmain.c
      src/glrender.c
      src/asound.c
      src/evgamepad.c
    )
endif()

# For Linux Framebuffer.
if(STRATO_TARGET_LINUX_GBM)
  set(STRATO_SOURCES
      src/image.c
      src/glyph.c
      src/wave.c
      src/stdfile.c
      src/gbmmain.c
      src/glrender.c
      src/asound.c
      src/evgamepad.c
    )
endif()

# For Emscripten.
if(STRATO_TARGET_WASM)
  set(STRATO_SOURCES
    src/image.c
    src/glyph.c
    src/wave.c
    src/stdfile.c
    src/emmain.c
    src/alsound.c
    src/glrender.c
  )
endif()

# For Emscripten (local file).
if(STRATO_TARGET_WASM_LOCAL)
  set(STRATO_SOURCES
    src/image.c
    src/glyph.c
    src/wave.c
    src/emfile.c
    src/emlocalmain.c
    src/alsound.c
    src/glrender.c
  )
endif()

# For iOS.
if(STRATO_TARGET_IOS)
  set(STRATO_SOURCES
    src/image.c
    src/glyph.c
    src/wave.c
    src/stdfile.c
    src/uimain.m
    src/aunit.c
    src/GameRenderer.m
    src/GameShaders.c
  )
endif()

# For Android.
if(STRATO_TARGET_ANDROID)
  set(STRATO_SOURCES
    src/image.c
    src/glyph.c
    src/wave.c
    src/glrender.c
    src/slsound.c
    src/ndkmain.c
    src/ndkfile.c
  )
endif()

# For Unity.
if(STRATO_TARGET_UNITY)
  set(STRATO_SOURCES
    src/image.c
    src/glyph.c
    src/wave.c
    src/halwrap.c
  )
endif()

# For translation.
if(STRATO_ENABLE_I18N)
  set(STRATO_I18N_SOURCES
    src/translation.c
  )
endif()

if(STRATO_ENABLE_SHARED)
  add_library(strato SHARED
    ${STRATO_SOURCES}
  )
elseif(STRATO_ENABLE_OBJECT)
  add_library(strato OBJECT
    ${STRATO_SOURCES}
    ${STRATO_I18N_SOURCES}
    $<TARGET_OBJECTS:png>
    $<TARGET_OBJECTS:jpeg>
    $<TARGET_OBJECTS:webp>
    $<TARGET_OBJECTS:vorbisfile>
    $<TARGET_OBJECTS:vorbis>
    $<TARGET_OBJECTS:ogg>
    $<TARGET_OBJECTS:freetype>
    $<TARGET_OBJECTS:brotlidec>
    $<TARGET_OBJECTS:brotlicommon>
    $<TARGET_OBJECTS:bz2>
    $<TARGET_OBJECTS:z>
  )
else()
  add_library(strato STATIC
    ${STRATO_SOURCES}
    ${STRATO_I18N_SOURCES}
    $<TARGET_OBJECTS:png>
    $<TARGET_OBJECTS:jpeg>
    $<TARGET_OBJECTS:webp>
    $<TARGET_OBJECTS:vorbisfile>
    $<TARGET_OBJECTS:vorbis>
    $<TARGET_OBJECTS:ogg>
    $<TARGET_OBJECTS:freetype>
    $<TARGET_OBJECTS:brotlidec>
    $<TARGET_OBJECTS:brotlicommon>
    $<TARGET_OBJECTS:bz2>
    $<TARGET_OBJECTS:z>
  )
endif()

# Windows specific.
if(STRATO_TARGET_WINDOWS)
  enable_language(RC)
  set(CMAKE_CXX_STANDARD 11)
  set(CMAKE_CXX_STANDARD_REQUIRED ON)
  file(ARCHIVE_EXTRACT
    INPUT       ${CMAKE_CURRENT_SOURCE_DIR}/lib/archive/dx12headers.tar.gz
    DESTINATION ${CMAKE_BINARY_DIR}
  )
endif()

# macOS specific
if(STRATO_TARGET_MACOS)
  enable_language(OBJC)
endif()

# For translation.
if(STRATO_ENABLE_I18N)
  target_compile_definitions(strato PRIVATE USE_TRANSLATION)
endif()

# For Unity
if(STRATO_TARGET_UNITY)
  target_compile_definitions(strato PRIVATE USE_UNITY USE_CSHARP)

  if(STRATO_TARGET_UNITY_SWITCH)
    target_compile_definitions(strato PRIVATE UNITY_SWITCH)
  elseif(STRATO_TARGET_UNITY_PS5)
    target_compile_definitions(strato PRIVATE UNITY_PS5)
  elseif(STRATO_TARGET_UNITY_XBOX)
    target_compile_definitions(strato PRIVATE UNITY_GAMECORE_XBOXSERIES)
  endif()
endif()

#
# Archiver Target
#

if(    NOT STRATO_TARGET_WASM
   AND NOT STRATO_TARGET_ANDROID
   AND NOT STRATO_TARGET_IOS
   AND NOT STRATO_TARGET_UNITY
)
  add_library(
    stratopack
    STATIC
    src/archive.c
  )
  target_include_directories(stratopack PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
endif()

#
# CPPFLAGS
#

# -Dinclude
target_include_directories(strato PUBLIC  ${CMAKE_CURRENT_SOURCE_DIR}/include)

# -DUSE_SHARED if we use dynamic linking.
if(STRATO_ENABLE_SHARED)
  target_compile_definitions(strato PRIVATE USE_SHARED)
endif()

# -DUSE_JIT if we use JIT.
if(STRATO_ENABLE_JIT)
  target_compile_definitions(strato PRIVATE USE_JIT)
endif()

# Add library header directories.
target_include_directories(strato PRIVATE ${PNG_INCLUDE_DIRS})
target_include_directories(strato PRIVATE ${JPEG_INCLUDE_DIRS})
target_include_directories(strato PRIVATE ${WEBP_INCLUDE_DIRS})
target_include_directories(strato PRIVATE ${OGG_INCLUDE_DIRS})
target_include_directories(strato PRIVATE ${VORBIS_INCLUDE_DIRS})
target_include_directories(strato PRIVATE ${FREETYPE_INCLUDE_DIRS})
target_include_directories(strato PRIVATE ${BROTLI_INCLUDE_DIRS})
target_include_directories(strato PRIVATE ${BZIP2_INCLUDE_DIRS})
target_include_directories(strato PRIVATE ${ZLIB_INCLUDE_DIRS})

# Windows: Add -Ires -Idx12headers/include/directx -Idx12headers/include/fxguids
if(STRATO_TARGET_WINDOWS)
  target_include_directories(strato PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/res
    ${CMAKE_BINARY_DIR}/dx12headers/include/directx
    ${CMAKE_BINARY_DIR}/dx12headers/include/dxguids
  )
endif()

# Linux Wayland: Add -DUSE_GLES -DUSE_WAYLAND
if(   STRATO_TARGET_LINUX_WAYLAND
   OR STRATO_TARGET_FREEBSD_WAYLAND)
  target_compile_definitions(strato PRIVATE USE_GLES USE_WAYLAND)
endif()

# Linux Framebuffer: Add -DUSE_GLES
if(STRATO_TARGET_LINUX_GBM)
  target_compile_definitions(strato PRIVATE USE_GLES)
endif()

# Linux Framebuffer
if(STRATO_TARGET_LINUX_GBM AND STRATO_ENABLE_ROT90)
  target_compile_definitions(strato PRIVATE USE_ROT90)
endif()

# FreeBSD: Add -I/usr/local/include
if(STRATO_TARGET_FREEBSD)
  target_include_directories(strato PUBLIC /usr/local/include)
endif()

# NetBSD: Add -I/usr/local/include -I/usr/X11R7/include
if(STRATO_TARGET_NETBSD)
  target_include_directories(strato PUBLIC /usr/local/include /usr/X11R7/include)
endif()

# OpenBSD: Add -I/usr/local/include -I/usr/X11R6/include
if(STRATO_TARGET_OPENBSD)
  target_include_directories(strato PUBLIC /usr/local/include /usr/X11R6/include)
endif()

# Android: Add -DUSE_GLES
if(STARATO_TARGET_ANDROID)
  target_compile_definitions(strato PRIVATE USE_GLES)
endif()

#
# CFLAGS
#

# macOS and iOS
if(STRATO_TARGET_MACOS OR STRATO_TARGET_IOS)
  target_compile_options(strato PUBLIC -fmodules)
endif()

# Windows compile options.
if(STRATO_TARGET_WINDOWS)
  # -municode
  if(MINGW)
    target_compile_options(strato PUBLIC -municode)
  endif()

  # /utf-8
  if(MSVC)
    target_compile_options(strato PUBLIC /utf-8)
    target_compile_definitions(strato PUBLIC UNICODE _UNICODE)
  endif()
endif()

#
# Link
#

# Libraries.
if(STRATO_ENABLE_SHARED)
  target_link_libraries(strato PUBLIC
    ${PNG_LIBRARIES}
    ${JPEG_LIBRARIES}
    ${WEBP_LIBRARIES}
    ${VORBIS_LIBRARY}
    ${VORBISFILE_LIBRARY}
    ${OGG_LIBRARY}
    ${FREETYPE_LIBRARY}
    ${BROTLIDEC_LIBRARIES}
    ${BROTLICOMMON_LIBRARIES}
    ${BZIP2_LIBRARIES}
    ${ZLIB_LIBRARIES}
  )
endif()

# Windows link options.
if(STRATO_TARGET_WINDOWS)
  # DLLs.
  target_link_libraries(strato PUBLIC
    gdi32
    ole32
    dsound
    dinput8
    xinput
    strmiids
    dxguid
  )

  # -mwindows -municode
  if(MINGW)
    target_link_options(strato PUBLIC
      -mwindows
      -municode
    )
  endif()

  # /SUBSYSTEM:WINDOWS
  if(MSVC)
    target_link_options(strato PUBLIC
      /SUBSYSTEM:WINDOWS
      /ENTRY:wWinMainCRTStartup
      /MANIFEST:NO
    )
  endif()
endif()

# macOS link options.
if(STRATO_TARGET_MACOS)
  target_link_libraries(strato PUBLIC
    "-framework AppKit"
    "-framework MetalKit"
    "-framework AVFoundation"
  )
endif()

# Linux and *BSD. (X11)
if(   STRATO_TARGET_LINUX
   OR STRATO_TARGET_FREEBSD
   OR STRATO_TARGET_NETBSD
   OR STRATO_TARGET_OPENBSD
)
  # Add -lm -lpthread -lXpm -lX11 -lGL -lGLX
  target_link_libraries(
    strato
    PUBLIC
    m
    pthread
    Xpm
    X11
    GL
    ${GLX_LIBRARY}  # We need this on systems with older Mesa.
  )
endif()

# Linux Wayland.
if(   STRATO_TARGET_LINUX_WAYLAND
   OR STRATO_TARGET_FREEBSD_WAYLAND)
  target_link_libraries(
    strato
    PUBLIC
    m
    pthread
    asound
    ${WAYLAND_LIBRARIES}
    ${EGL_LIBRARIES}
    ${GLES2_LIBRARIES}
  )
endif()

# Linux framebuffer.
if(STRATO_TARGET_LINUX_GBM)
  # Add -lm -lpthread -lgbm -ldrm -lEGL -lGLESv2 -lasound
  target_link_libraries(
    strato
    PUBLIC
    m
    pthread
    gbm
    drm
    EGL
    GLESv2
    asound
  )
  target_include_directories(strato PUBLIC /usr/include/libdrm)
endif()

# Linux X11: Add -lasound
if(STRATO_TARGET_LINUX)
  target_link_libraries(strato PUBLIC asound)
endif()

# FreeBSD: Add -L/usr/local/lib -L/usr/X11R7/lib
if(STRATO_TARGET_FREEBSD)
  target_link_directories(strato PUBLIC /usr/local/lib /usr/X11R7/lib)
endif()

# NetBSD: Add -L/usr/local/lib -L/usr/X11R7/lib
if(STRATO_TARGET_NETBSD)
  target_link_directories(strato PUBLIC /usr/local/lib /usr/X11R7/lib)
endif()

# OpenBSD: Add -L/usr/local/lib -L/usr/X11R6/lib
if(STRATO_TARGET_OPENBSD)
  target_link_directories(strato PUBLIC /usr/local/lib /usr/X11R6/lib)
endif()

# Android: Add -lGLESv3 -lOpenSLES -llog
if(STRATO_TARGET_ANDROID)
  target_link_libraries(strato PUBLIC -lm -lGLESv3 -lOpenSLES -llog)
endif()

#
# Custom
#

if(   STRATO_TARGET_LINUX_WAYLAND
   OR STRATO_TARGET_FREEBSD_WAYLAND)
  find_program(WAYLAND_SCANNER NAMES wayland-scanner REQUIRED)

  set(XDG_SHELL_XML
      /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml)
  set(XDG_DECORATION_XML
      /usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml)

  # generate xdg-shell sources
  set(GEN_SHELL_HDR ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell-client-protocol.h)
  set(GEN_SHELL_SRC ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell-protocol.c)
  add_custom_command(
    OUTPUT ${GEN_SHELL_HDR} ${GEN_SHELL_SRC}
    COMMAND ${WAYLAND_SCANNER} client-header ${XDG_SHELL_XML} ${GEN_SHELL_HDR}
    COMMAND ${WAYLAND_SCANNER} private-code ${XDG_SHELL_XML} ${GEN_SHELL_SRC}
    DEPENDS ${XDG_SHELL_XML}
  )

  # generate xdg-decoration sources
  set(GEN_DECORATION_HDR ${CMAKE_CURRENT_BINARY_DIR}/zxdg-decoration-unstable-v1-client-protocol.h)
  set(GEN_DECORATION_SRC ${CMAKE_CURRENT_BINARY_DIR}/zxdg-decoration-unstable-v1-protocol.c)
  add_custom_command(
    OUTPUT ${GEN_DECORATION_HDR} ${GEN_DECORATION_SRC}
    COMMAND ${WAYLAND_SCANNER} client-header ${XDG_DECORATION_XML} ${GEN_DECORATION_HDR}
    COMMAND ${WAYLAND_SCANNER} private-code ${XDG_DECORATION_XML} ${GEN_DECORATION_SRC}
    DEPENDS ${XDG_DECORATION_XML}
  )

  # add both generated sources
  target_sources(strato PRIVATE
    ${GEN_SHELL_SRC} ${GEN_SHELL_HDR}
    ${GEN_DECORATION_SRC} ${GEN_DECORATION_HDR}
  )
  target_include_directories(strato PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
endif()
