cmake_minimum_required(VERSION 3.24)

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

#add_definitions(-DJUCE_ENABLE_REPAINT_DEBUGGING)
#add_compile_definitions(JUCE_IOS_AUDIO_LOGGING=1)

include(FetchContent)
include(cmake/ResourceBundling.cmake)
include(cmake/ASIOSDK.cmake)
include(cmake/ClangFormat.cmake)
include(cmake/FetchContentCache.cmake)
include(cmake/AndroidJavaSources.cmake)

find_package(Git)

if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/VERSION")
  message(FATAL_ERROR "VERSION file not found at ${CMAKE_CURRENT_SOURCE_DIR}/VERSION")
endif()
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/VERSION" VMPC_VERSION LIMIT_COUNT 1)
string(STRIP "${VMPC_VERSION}" VMPC_VERSION)
if("${VMPC_VERSION}" STREQUAL "")
  message(FATAL_ERROR "VERSION file is empty")
endif()


set(CMAKE_CXX_STANDARD 17)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_SKIP_BUILD_RPATH ON)
set(CMAKE_SUPPRESS_REGENERATION true)

set(VMPC_TARGET_SYSTEM_NAME "${CMAKE_SYSTEM_NAME}")
if(VMPC_TARGET_SYSTEM_NAME STREQUAL "")
  set(VMPC_TARGET_SYSTEM_NAME "${CMAKE_HOST_SYSTEM_NAME}")
endif()

if(VMPC_TARGET_SYSTEM_NAME STREQUAL "Darwin" OR VMPC_TARGET_SYSTEM_NAME STREQUAL "iOS")
  if(VMPC_TARGET_SYSTEM_NAME STREQUAL "iOS")
    set(CMAKE_OSX_DEPLOYMENT_TARGET "15.0" CACHE INTERNAL "Minimum iOS deployment version")
    # Dependencies set their own cached minimum; preserve this project's target.
    set(CMAKE_OSX_DEPLOYMENT_TARGET "15.0")
  else()
    set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE INTERNAL "Minimum OS X deployment version")
  endif()
endif()

set (JUCE_ENABLE_MODULE_SOURCE_GROUPS ON)
if(NOT DEFINED JUCE_COPY_PLUGIN_AFTER_BUILD)
  set(JUCE_COPY_PLUGIN_AFTER_BUILD ON CACHE BOOL "Copy JUCE plugin after build")
endif()
set_property(GLOBAL PROPERTY USE_FOLDERS YES)

if(VMPC_TARGET_SYSTEM_NAME STREQUAL "Darwin" OR VMPC_TARGET_SYSTEM_NAME STREQUAL "iOS")
    project(vmpc2000xl VERSION ${VMPC_VERSION} LANGUAGES C CXX OBJC OBJCXX)
else()
    project(vmpc2000xl VERSION ${VMPC_VERSION} LANGUAGES C CXX)
endif()

option(VMPC_BUILD_CCACHE_ENABLED "Enable compiler cache integration via find_package(ccache)" OFF)
if(VMPC_BUILD_CCACHE_ENABLED)
  find_package(ccache)
endif()

if(MSVC)
    set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>" CACHE INTERNAL "")
endif()

string(TIMESTAMP CONFIGURE_TIMESTAMP "%a, %d %b %Y %H:%M:%S")

file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/generated/build_info")
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/generated/build_info/VmpcJuceBuildInfo.hpp"
  "#pragma once\nnamespace vmpc_juce::build_info { inline const char *getVersionString(){ return \"${VMPC_VERSION}\"; } inline const char *getTimeStampString(){ return \"${CONFIGURE_TIMESTAMP}\"; }}")

# The version.txt file can be picked up by the CI/CD, so it can inject version info
# into binaries, installers, GitHub releases, etc.
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/version.txt "${VMPC_VERSION}")

set(FETCHCONTENT_UPDATES_DISCONNECTED ON)

option(VMPC_BUILD_TESTS "Build vmpc-juce test targets" ON)

set(VMPC_JUCE_REPO "https://github.com/izzyreal/JUCE.git")
set(VMPC_JUCE_REF "7.0.9-vmpc")
set(VMPC_MELATONIN_BLUR_REPO "https://github.com/sudara/melatonin_blur.git")
set(VMPC_MELATONIN_BLUR_REF "main")
set(VMPC_RAW_KBD_REPO "https://github.com/izzyreal/juce-raw-keyboard-input-module.git")
set(VMPC_RAW_KBD_REF "external-focus-provider")
set(VMPC_MPC_REPO "https://github.com/izzyreal/mpc.git")
set(VMPC_MPC_REF "master")

if(NOT DEFINED FETCHCONTENT_CACHE_ROOT OR "${FETCHCONTENT_CACHE_ROOT}" STREQUAL "")
  if(DEFINED VMPC_FETCHCONTENT_SOURCES_DIR AND NOT "${VMPC_FETCHCONTENT_SOURCES_DIR}" STREQUAL "")
    set(FETCHCONTENT_CACHE_ROOT "${VMPC_FETCHCONTENT_SOURCES_DIR}")
  endif()
endif()

fetchcontent_declare_cached_git(JUCE "JUCE" "${CMAKE_SOURCE_DIR}/editables/JUCE" "${VMPC_JUCE_REPO}" "${VMPC_JUCE_REF}"
  GIT_SHALLOW    ON
)

fetchcontent_declare_cached_git(melatonin_blur "melatonin_blur" "${CMAKE_SOURCE_DIR}/deps/melatonin_blur" "${VMPC_MELATONIN_BLUR_REPO}" "${VMPC_MELATONIN_BLUR_REF}")

fetchcontent_declare_cached_git(juce-raw-keyboard-input-module "juce-raw-keyboard-input-module" "${CMAKE_SOURCE_DIR}/editables/juce-raw-keyboard-input-module" "${VMPC_RAW_KBD_REPO}" "${VMPC_RAW_KBD_REF}")

fetchcontent_declare_cached_url(json "json-src" "${CMAKE_SOURCE_DIR}/deps/json" "https://github.com/nlohmann/json/releases/download/v3.12.0/json.tar.xz"
  DOWNLOAD_EXTRACT_TIMESTAMP TRUE)

fetchcontent_declare_cached_git(mpc "mpc" "${CMAKE_SOURCE_DIR}/editables/mpc" "${VMPC_MPC_REPO}" "${VMPC_MPC_REF}")

set(BUILD_EXAMPLES OFF)
set(BUILD_TESTS OFF)
set(INSTALL_PROJECT OFF)

message(STATUS "Resolving json...")
FetchContent_MakeAvailable(json)
if(TARGET nlohmann_json::nlohmann_json AND NOT TARGET nlohmann_json)
  add_library(nlohmann_json INTERFACE IMPORTED)
  set_property(TARGET nlohmann_json PROPERTY INTERFACE_LINK_LIBRARIES nlohmann_json::nlohmann_json)
endif()

if(CMAKE_SYSTEM_NAME STREQUAL "iOS")
  # Resolve system libraries against the SDK selected at build time.
  add_library(ZLIB::ZLIB INTERFACE IMPORTED)
  set_target_properties(ZLIB::ZLIB PROPERTIES
    IMPORTED_LIBNAME z)

  add_library(Iconv::Iconv INTERFACE IMPORTED)
  set_target_properties(Iconv::Iconv PROPERTIES
    IMPORTED_LIBNAME iconv)
endif()

message(STATUS "Resolving mpc...")
FetchContent_MakeAvailable(mpc)

message(STATUS "Resolving JUCE...")
FetchContent_MakeAvailable(JUCE)

message(STATUS "Resolving juce-raw-keyboard-input-module...")
FetchContent_MakeAvailable(juce-raw-keyboard-input-module)

if(ANDROID)
  FetchContent_GetProperties(JUCE SOURCE_DIR _android_juce_source_dir)
  FetchContent_GetProperties(
    juce-raw-keyboard-input-module
    SOURCE_DIR _android_raw_keyboard_source_dir)
  vmpc_stage_android_java_sources(
    "${_android_juce_source_dir}"
    "${_android_raw_keyboard_source_dir}")
endif()

message(STATUS "Resolving melatonin_blur...")
FetchContent_MakeAvailable(melatonin_blur)

if(WIN32)
  # Prevent windows.h from defining min/max macros that break std::min/std::max.
  add_compile_definitions(NOMINMAX)
endif()

if (IOS)
    set(IOS_SPECIFIC_SETTINGS
        REQUIRES_FULL_SCREEN TRUE
        IPHONE_SCREEN_ORIENTATIONS UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight
        IPAD_SCREEN_ORIENTATIONS UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight
        BACKGROUND_AUDIO_ENABLED TRUE
        APP_GROUPS_ENABLED TRUE
        APP_GROUP_IDS group.nl.izmar.vmpc2000xl
        FILE_SHARING_ENABLED TRUE
        DOCUMENT_BROWSER_ENABLED TRUE
        TARGETED_DEVICE_FAMILY "1,2"
        FORMATS AUv3 Standalone)
elseif(ANDROID)
    set(ANDROID_SPECIFIC_SETTINGS
        FORMATS Standalone)
else()
    set(NON_IOS_SPECIFIC_SETTINGS
        HARDENED_RUNTIME_ENABLED TRUE
        HARDENED_RUNTIME_OPTIONS com.apple.security.device.audio-input com.apple.security.device.bluetooth
        VST3_CATEGORIES Instrument Drum OnlyRT Sampler Stereo
        VST_NUM_MIDI_INS 1
        VST_NUM_MIDI_OUTS 1
        FORMATS LV2 VST3 AU AUv3 Standalone
        LV2URI "http://www.izmar.nl/VMPC2000XL")
endif()

# Single call to juce_add_plugin with conditional settings
juce_add_plugin(vmpc2000xl
    BUNDLE_ID nl.izmar.vmpc2000xl
    AU_MAIN_TYPE kAudioUnitType_MusicEffect
    ICON_BIG "src/main/resources/icon.png"
    COMPANY_NAME Izmar
    IS_SYNTH TRUE
    NEEDS_MIDI_INPUT TRUE
    NEEDS_MIDI_OUTPUT TRUE
    EDITOR_WANTS_KEYBOARD_FOCUS TRUE
    PLUGIN_MANUFACTURER_CODE Izmr
    PLUGIN_CODE 2kXL
    MICROPHONE_PERMISSION_ENABLED TRUE
    MICROPHONE_PERMISSION_TEXT "Allow VMPC2000XL to record from the microphone?"
    BLUETOOTH_PERMISSION_ENABLED TRUE
    BLUETOOTH_PERMISSION_TEXT "This enables VMPC2000XL to use Bluetooth MIDI devices"
    PRODUCT_NAME "VMPC2000XL"
    ${IOS_SPECIFIC_SETTINGS}
    ${ANDROID_SPECIFIC_SETTINGS}
    ${NON_IOS_SPECIFIC_SETTINGS})

if(ANDROID)
    # JUCE's Android loader looks for libjuce_jni.so. The target name remains
    # stable for the rest of the repository; only the packaged filename changes.
    set_target_properties(vmpc2000xl_Standalone PROPERTIES
        OUTPUT_NAME juce_jni)
endif()

if (IOS)
    # "Designed for iPad on Mac" launch requires the embedded AUv3 appex to be
    # signed when copied into the standalone app bundle.
    set_target_properties(vmpc2000xl_Standalone PROPERTIES
        XCODE_EMBED_APP_EXTENSIONS_CODE_SIGN_ON_COPY "YES")
endif()

if (APPLE AND NOT IOS)
    set_target_properties(vmpc2000xl_Standalone PROPERTIES
        SKIP_BUILD_RPATH FALSE
        BUILD_RPATH "${CMAKE_BINARY_DIR}/_deps/json-schema-validator-build/$<CONFIG>;${CMAKE_BINARY_DIR}/_deps/mpc-build/$<CONFIG>")
endif()

if (IOS)
    function(add_dsym target)
        set_target_properties(${target} PROPERTIES
            XCODE_ATTRIBUTE_CONFIGURATION_BUILD_DIR "$(inherited)"
            XCODE_ATTRIBUTE_DEPLOYMENT_POSTPROCESSING[variant=RelWithDebInfo] "YES"
            XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf-with-dsym"
            XCODE_ATTRIBUTE_GCC_GENERATE_DEBUGGING_SYMBOLS "YES")
    endfunction()

    add_dsym(vmpc2000xl_Standalone)
    #add_dsym(vmpc2000xl_AUv3)
endif()

set(_src_root_path "${CMAKE_CURRENT_SOURCE_DIR}/src/main")

target_include_directories(vmpc2000xl PUBLIC
  ${_src_root_path}
  ${CMAKE_CURRENT_BINARY_DIR}/generated
  ${mpc_SOURCE_DIR}/src/main
)

if (APPLE)
  file(
    GLOB_RECURSE _source_list
    CONFIGURE_DEPENDS
    LIST_DIRECTORIES false
    RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
    "${_src_root_path}/*.c"
    "${_src_root_path}/*.c*"
    "${_src_root_path}/*.h*"
    "${_src_root_path}/*.m*"
  )
else()
  file(
    GLOB_RECURSE _source_list
    CONFIGURE_DEPENDS
    LIST_DIRECTORIES false
    RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
    "${_src_root_path}/*.c*"
    "${_src_root_path}/*.h*"
  )
endif()

if(ANDROID)
  list(REMOVE_ITEM _source_list "src/main/standalone/StandaloneApp.cpp")
endif()

foreach(_source IN ITEMS ${_source_list})
    get_filename_component(_source_path "${_source}" PATH)
    string(REPLACE "/" "\\" _source_path_msvc "${_source_path}")
    source_group("${_source_path_msvc}" FILES "${_source}")
endforeach()

target_sources(vmpc2000xl PRIVATE ${_source_list})

if(ANDROID)
  # JUCE normally owns the standalone entry point. Android needs the
  # application factory in the shared object, so compile our app entry point
  # into the wrapper target that actually produces libjuce_jni.so.
  target_sources(vmpc2000xl_Standalone PRIVATE
    src/main/standalone/StandaloneApp.cpp)
endif()

target_compile_definitions(vmpc2000xl
    PUBLIC
    JUCE_WEB_BROWSER=0
    JUCE_USE_CURL=0
    JUCE_VST3_CAN_REPLACE_VST2=0
    JUCE_DISPLAY_SPLASH_SCREEN=0
    JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1)

if(IOS OR ANDROID)
    target_compile_definitions(vmpc2000xl PUBLIC JUCE_CONTENT_SHARING=1)
endif()

if(UNIX AND NOT APPLE AND NOT ANDROID)
    target_compile_definitions(vmpc2000xl PUBLIC JUCE_JACK=1)
    include(FindPkgConfig)
    pkg_search_module(udisks2 REQUIRED udisks2)
    target_link_libraries(vmpc2000xl PRIVATE ${udisks2_LIBRARIES} X11)
endif()

if (IOS)
  set_target_properties(vmpc2000xl_Standalone PROPERTIES
    XCODE_EXPLICIT_FILE_TYPE "wrapper.application"
  )
endif()

target_link_libraries(vmpc2000xl
  PRIVATE
    juce::juce_audio_utils
    nlohmann_json::nlohmann_json
    melatonin_blur
    raw_keyboard_input
    mpc
  PUBLIC
    juce::juce_recommended_config_flags
    juce::juce_recommended_lto_flags
    juce::juce_recommended_warning_flags)

if (VMPC_BUILD_TESTS)
  file(
    GLOB_RECURSE _vmpc_test_sources
    LIST_DIRECTORIES false
    RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
    "src/test/*.cpp"
  )

  add_executable(vmpc-juce-tests ${_vmpc_test_sources})

  target_include_directories(vmpc-juce-tests PRIVATE
    ${_src_root_path}
    ${CMAKE_CURRENT_BINARY_DIR}/generated
    ${mpc_SOURCE_DIR}/src/main
  )

  target_link_libraries(vmpc-juce-tests
    PRIVATE
      Catch2::Catch2WithMain
      juce::juce_gui_basics
      juce::juce_core
      nlohmann_json::nlohmann_json
      mpc
  )

  target_compile_definitions(vmpc-juce-tests
    PRIVATE
      JUCE_WEB_BROWSER=0
      JUCE_USE_CURL=0
  )

  target_sources(vmpc-juce-tests PRIVATE
    src/main/gui/LcdInteraction.cpp
    src/main/ZipSaveTarget.cpp
    src/main/VmpcJuceResourceUtil.cpp
  )
endif()

_bundle_vmpc_juce_resources(vmpc2000xl)

include(cmake/guilab.cmake)

if(CMAKE_GENERATOR STREQUAL "Xcode")
  # Generating a scheme for any target disables Xcode's automatic schemes for
  # the rest of the project. Keep every target used by CI explicit so adding a
  # new app or test scheme cannot make an existing CI scheme disappear.
  set(_vmpc_xcode_scheme_targets
    vmpc2000xl_Standalone
    vmpc2000xl_AU
    vmpc2000xl_AUv3
    vmpc2000xl_VST3
    vmpc2000xl_LV2
    vmpc2000xl_GuiLab
    mpc-tests
    vmpc-juce-tests
    vmpc-juce-guilab-tests)

  foreach(_target IN LISTS _vmpc_xcode_scheme_targets)
    if(TARGET ${_target})
      set_property(TARGET ${_target} PROPERTY XCODE_GENERATE_SCHEME TRUE)
    endif()
  endforeach()
endif()

if (VMPC_BUILD_TESTS AND TARGET vmpcjuce::rc)
  target_link_libraries(vmpc-juce-tests PRIVATE vmpcjuce::rc)
endif()

if (APPLE)
  execute_process(
    COMMAND python3 macos-codesign-details-extractor.py
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    OUTPUT_VARIABLE DEVELOPMENT_TEAM)
  set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "${DEVELOPMENT_TEAM}")
  message("\nSetting macOS and iOS development team to organisational unit: ${DEVELOPMENT_TEAM}\n"
    "This is derived from the first valid Apple Development certificate in your local keychain.\n"
    "You can tweak it in the Build Settings tab of your root project.\n")

if (NOT IOS)
    target_link_libraries(vmpc2000xl PRIVATE "-framework Carbon")
endif()
endif()

if(MSVC)
    target_compile_definitions(vmpc2000xl PUBLIC JUCE_ASIO=1)
    getAndIncludeASIOSDK(asiosdk_2.3.3_2019-06-14)
    target_compile_options(vmpc2000xl PRIVATE "/MP")
    set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>" CACHE INTERNAL "")
endif()

if(GIT_FOUND AND NOT ANDROID)
  if (EXISTS ${CMAKE_SOURCE_DIR}/editables/mpc/.git)
    execute_process(COMMAND ${GIT_EXECUTABLE} remote set-url --push origin git@github.com:izzyreal/mpc
                    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/editables/mpc
                    RESULT_VARIABLE GIT_REMOTE_RESULT)
    if(NOT GIT_REMOTE_RESULT EQUAL "0")
        message(FATAL_ERROR "git remote set-url --push origin git@github.com:izzyreal/mpc failed with ${GIT_REMOTE_RESULT}. Please contact your local Git guru if you want to push changes.")
    endif()
  endif()
endif()

if(PROJECT_IS_TOP_LEVEL AND NOT ANDROID)
    # Create symlink to compile_commands.json for IDE to pick it up
    execute_process(
        COMMAND ${CMAKE_COMMAND} -E create_symlink
            ${CMAKE_BINARY_DIR}/compile_commands.json
            ${CMAKE_CURRENT_SOURCE_DIR}/compile_commands.json
    )
endif()

set(CPACK_SOURCE_GENERATOR "TGZ")
set(CPACK_SOURCE_IGNORE_FILES
    /.git
    /build
    /\\\\.DS_Store
)
include(CPack)
