# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: Copyright (C) 2026 Max A. Vilkki a.k.a. codise
#
# libsidplayfp (GPL-2.0-or-later) from lib/libsidplayfp (v3.1.1): the C64 emulation
# (CPU, CIA, VIC-II, PLA), tune loaders and player, with the reSIDfp builder on top of
# ext::residfp.
#
# Upstream builds with autotools. This mirrors src_libsidplayfp_la_SOURCES and the
# residfp builder's sources in its Makefile.am. generated/ holds what the release tarball
# and configure add to the git tree (see generated/README.md, refreshed by
# update_generated.sh).
#
# Left out:
# - src/utils/SidDatabase.cpp, iniParser.cpp (Songlengths) and libstilview (STIL): we
#   parse HVSC's files ourselves, and nothing else in the library uses them.
# - The sidlite builder (cRSID-based): upstream always builds it into the library, but
#   only its own builder class refers to it, and we use reSIDfp and classic reSID.
# - The exSID and USBSID-Pico hardware builders (their drivers aren't in the clone) and
#   the tests and demo programs.
#
# Targets:
#   ext::sidplayfp           the library; users include <sidplayfp/sidplayfp.h>,
#                            <sidplayfp/SidTune.h>, ... and <sidplayfp/builders/residfp.h>,
#                            the layout `make install` produces minus the headers of
#                            what we leave out (builders/sidlite.h, SidDatabase.h).
#   ext::sidplayfp_internal  adds the private headers (sidemu.h, Event.h, c64/...) for our
#                            own sidbuilder (classic reSID). Only that code should use it.
#
# Size (Release, AppleClang 17, arm64): about 125 KB of code and constants per
# architecture before dead-stripping. With ext::residfp it adds about 175 KB to a
# stripped executable (about 350 KB for a universal macOS binary).

if(NOT DEFINED FLIPSID_LIB_DIR)
    set(FLIPSID_LIB_DIR "${CMAKE_CURRENT_LIST_DIR}/../..")
endif()
set(sidplayfpDir "${FLIPSID_LIB_DIR}/libsidplayfp")
set(sidplayfpGenerated "${CMAKE_CURRENT_LIST_DIR}/generated")

if(NOT EXISTS "${sidplayfpDir}/configure.ac")
    message(FATAL_ERROR
        "libsidplayfp: sources not found in ${sidplayfpDir}.\n"
        "Run lib/fetch_externals.sh (lib\\fetch_externals.cmd on Windows), then configure again.")
endif()
if(NOT TARGET ext::residfp)
    message(FATAL_ERROR "libsidplayfp: add_subdirectory(lib/cmake/libresidfp) must come first.")
endif()

# generated/ has to match the clone: the .bin drivers and config.h come from one release.
function(flipsid_sidplayfp_check_version)
    file(READ "${sidplayfpDir}/configure.ac" configureAc)
    file(READ "${sidplayfpGenerated}/src/sidplayfp/sidversion.h" versionHeader)
    foreach(part major minor level)
        string(REGEX MATCH "m4_define\\(\\[lib_${part}\\], \\[([0-9]+)\\]\\)" _ "${configureAc}")
        list(APPEND cloneVersion "${CMAKE_MATCH_1}")
    endforeach()
    foreach(part MAJ MIN LEV)
        string(REGEX MATCH "LIBSIDPLAYFP_VERSION_${part} ([0-9]+)" _ "${versionHeader}")
        list(APPEND generatedVersion "${CMAKE_MATCH_1}")
    endforeach()
    list(JOIN cloneVersion "." cloneVersion)
    list(JOIN generatedVersion "." generatedVersion)
    if(NOT cloneVersion STREQUAL generatedVersion)
        message(FATAL_ERROR
            "libsidplayfp: lib/libsidplayfp is ${cloneVersion} but "
            "lib/cmake/libsidplayfp/generated is for ${generatedVersion}.\n"
            "Run lib/cmake/libsidplayfp/update_generated.sh ${cloneVersion} and check "
            "Makefile.am's source lists against lib/cmake/libsidplayfp/CMakeLists.txt.")
    endif()
endfunction()
# file(READ) doesn't make CMake watch a file. Without this, a pin bump or a refreshed
# generated/ would go unchecked until someone configured by hand, and the build would
# pair one version's sources with another's generated files.
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
    "${sidplayfpDir}/configure.ac"
    "${sidplayfpDir}/Makefile.am"
    "${sidplayfpGenerated}/src/sidplayfp/sidversion.h"
)
flipsid_sidplayfp_check_version()

set(sidplayfpSources
    # src_libsidplayfp_la_SOURCES, compiled files only, minus src/utils/*.
    src/EventScheduler.cpp
    src/player.cpp
    src/psiddrv.cpp
    src/reloc65.cpp
    src/simpleMixer.cpp
    src/sidemu.cpp
    src/c64/c64.cpp
    src/c64/mmu.cpp
    src/c64/VIC_II/mos656x.cpp
    src/c64/CPU/mos6510.cpp
    src/c64/CPU/mos6510debug.cpp
    src/c64/CIA/interrupt.cpp
    src/c64/CIA/mos652x.cpp
    src/c64/CIA/SerialPort.cpp
    src/c64/CIA/timer.cpp
    src/c64/CIA/tod.cpp
    src/sidplayfp/sidplayfp.cpp
    src/sidplayfp/sidbuilder.cpp
    src/sidplayfp/SidConfig.cpp
    src/sidplayfp/SidInfo.cpp
    src/sidplayfp/SidTune.cpp
    src/sidplayfp/SidTuneInfo.cpp
    src/sidtune/MUS.cpp
    src/sidtune/p00.cpp
    src/sidtune/prg.cpp
    src/sidtune/PSID.cpp
    src/sidtune/SidTuneBase.cpp
    src/sidtune/SidTuneTools.cpp
    # src_builders_residfp_builder_libsidplayfp_residfp_la_SOURCES
    src/builders/residfp-builder/residfp-builder.cpp
    src/builders/residfp-builder/residfp-emu.cpp
)
list(TRANSFORM sidplayfpSources PREPEND "${sidplayfpDir}/")

add_library(ext_sidplayfp STATIC ${sidplayfpSources})
add_library(ext::sidplayfp ALIAS ext_sidplayfp)

# configure asks for the newest standard the compiler has (C++23 down to C++11), without
# GNU extensions. C++20 is ours; C++23 would only switch a few lines to
# std::to_underlying and std::unreachable.
target_compile_features(ext_sidplayfp PRIVATE cxx_std_20)
set_target_properties(ext_sidplayfp PROPERTIES
    OUTPUT_NAME sidplayfp
    CXX_EXTENSIONS OFF
    CXX_VISIBILITY_PRESET hidden
    VISIBILITY_INLINES_HIDDEN ON
)

# Makefile.am's AM_CPPFLAGS: the build dir (our generated/) before the source dir.
# psiddrv.cpp includes "psiddrv.bin" and sidtune/MUS.cpp "sidplayer1.bin", which exist
# only in generated/src and generated/src/sidtune.
target_include_directories(ext_sidplayfp PRIVATE
    "${sidplayfpGenerated}/src"
    "${sidplayfpGenerated}/src/sidtune"
    "${sidplayfpDir}/src"
)
# NDEBUG in every build type, as configure does unless --enable-debug. Without it, each
# sidplayfp instance prints the CPU's 256-entry opcode table to stdout, and all we'd gain
# is one assert (a mixer clipping check). The headers don't depend on it.
target_compile_definitions(ext_sidplayfp PRIVATE HAVE_CONFIG_H NDEBUG)

# The residfp builder includes <residfp/residfp.h>. Static, so ext::residfp still ends up
# on the link line of everything that links us.
target_link_libraries(ext_sidplayfp PRIVATE ext::residfp)

# The installed header layout, through forwarding headers written at configure time
# (they need the absolute path of FLIPSID_LIB_DIR). This keeps upstream's private
# headers out of every user's include path: player.h, Event.h and c64/ would be too easy
# to pick up by accident.
#
# Clang and GCC treat a header included from a system header as a system header too.
# MSVC decides by the directory a header is found in (/external:I), so the upstream
# headers behind these would get our /W4 (e.g. C4267 in sidbuilder.h), hence the pragma.
set(sidplayfpInclude "${CMAKE_CURRENT_BINARY_DIR}/include")
set(sidplayfpForwardTemplate [=[
// Written by lib/cmake/libsidplayfp/CMakeLists.txt.
#ifdef _MSC_VER
#pragma warning(push, 0)
#endif
#include "@forwardTo@"
#ifdef _MSC_VER
#pragma warning(pop)
#endif
]=])
function(flipsid_sidplayfp_forward header forwardTo)
    file(CONFIGURE OUTPUT "${sidplayfpInclude}/sidplayfp/${header}"
        CONTENT "${sidplayfpForwardTemplate}" @ONLY)
endfunction()
foreach(header sidplayfp.h SidTune.h SidConfig.h SidInfo.h SidTuneInfo.h sidbuilder.h siddefs.h)
    flipsid_sidplayfp_forward(${header} "${sidplayfpDir}/src/sidplayfp/${header}")
endforeach()
flipsid_sidplayfp_forward(sidversion.h "${sidplayfpGenerated}/src/sidplayfp/sidversion.h")
flipsid_sidplayfp_forward(builders/residfp.h
    "${sidplayfpDir}/src/builders/residfp-builder/residfp.h")
target_include_directories(ext_sidplayfp SYSTEM INTERFACE "${sidplayfpInclude}")

if(MSVC)
    # /Zc:__cplusplus: siddefs.h picks attributes by __cplusplus. config.h copes without.
    # /utf-8: a few comments aren't ASCII. /w: third-party warnings aren't ours to fix.
    target_compile_options(ext_sidplayfp PRIVATE /Zc:__cplusplus /utf-8 /w)
else()
    target_compile_options(ext_sidplayfp PRIVATE -w)
endif()
# Not replicated: --enable-lto (default off; the top level decides), and MinGW's
# -DWIN32_LEAN_AND_MEAN, as none of the files we build includes <windows.h>.

if(COMMAND flipsid_configure_third_party)
    flipsid_configure_third_party(ext_sidplayfp)
endif()

# Private headers for our own sidbuilder. HAVE_CONFIG_H because sidcxx11.h, which most
# of them include, needs config.h for the HAVE_CXXnn macros. Note that config.h defines
# PACKAGE, VERSION and friends in every file that uses this target.
add_library(ext_sidplayfp_internal INTERFACE)
add_library(ext::sidplayfp_internal ALIAS ext_sidplayfp_internal)
target_include_directories(ext_sidplayfp_internal SYSTEM INTERFACE
    "${sidplayfpGenerated}/src"
    "${sidplayfpDir}/src"
)
target_compile_definitions(ext_sidplayfp_internal INTERFACE HAVE_CONFIG_H)
target_compile_features(ext_sidplayfp_internal INTERFACE cxx_std_20)
target_link_libraries(ext_sidplayfp_internal INTERFACE ext::sidplayfp)
