# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: Copyright (C) 2026 Max A. Vilkki a.k.a. codise
#
# Classic reSID 1.0 (GPL-2.0-or-later) from lib/resid: Dag Lem's MOS6581/8580 emulation as
# VICE maintains it, pinned to the commit libsidplayfp 2.16.1 bundles. libsidplayfp 3.x
# dropped its reSID builder; ours lives in src/core/engines (DESIGN §2.4).
#
# Upstream builds with autotools. This mirrors libresid_a_SOURCES in its Makefile.am, and
# generated/ holds the siddefs.h configure would make (see generated/README.md, refreshed
# by update_generated.sh). The waveform tables that the upstream build converts from
# .dat files with Perl are converted here instead, at configure time.
#
# The filter: generated/siddefs.h sets NEW_8580_FILTER to 1, configure's default, so this
# compiles filter8580new.cc (Leandro Nini's transistor-level 8580 model and his 6581
# changes: input noise, mixer gain, resonance tables), as VICE, libsidplayfp 2.16 and
# sid-device do, rather than Dag Lem's filter.cc (a linear 8580 filter up to 12.5 kHz),
# which JSIDPlay2's reSID ports (DESIGN §2.4). Both define reSID::Filter, so only one can
# be linked. To switch, set NEW_8580_FILTER in update_generated.sh and regenerate; the
# source list below follows the header.
#
# filter8580new.h fills each filter's input-noise table with rand(). resid_rand.cc (ours,
# included by the generated siddefs.h) gives reSID a per-thread, seedable rand() of its
# own instead of the C library's process-wide one; see resid_rand.h. filter8580new.cc also
# sets the 6581 filter bias and the 8580 gate voltage only in the first Filter it builds;
# our builder calls adjust_filter_bias() on every chip (src/core/engines/resid_emu.cpp).
#
# Target:
#   ext::resid   the library. Users include <resid/sid.h> (and the rest of reSID's
#                headers under resid/). The prefix matters: libresidfp has SID.h, Filter.h,
#                Voice.h... which would collide with sid.h, filter.h, voice.h on
#                case-insensitive file systems. Those stay private to ext::residfp, and
#                reSID's headers are only reachable as resid/<name>.
#                Everything is in namespace reSID (libresidfp uses reSIDfp); the one C
#                symbol is resid_version_string (libresidfp's is residfp_version_string).
#
# Size (Release, AppleClang 17, arm64, 2026-09): with our builder, it adds 183 KB to a
# stripped executable that already plays through reSIDfp: 30 KB of code and 128 KB of
# waveform tables (WaveformGenerator::model_wave, initialised data). That was measured
# with filter.cc; filter8580new.cc adds another 17 KB. Its filter model also has 21.5 MB
# of zero-initialised tables (filter.cc: 17 MB), which become resident when the first reSID
# chip is created (430 ms, once per process). They cost no file size as long as they stay
# in .bss: MinGW GCC's -fdata-sections would move them into the file (flipsid_options.cmake).

if(NOT DEFINED FLIPSID_LIB_DIR)
    set(FLIPSID_LIB_DIR "${CMAKE_CURRENT_LIST_DIR}/../..")
endif()
set(residDir "${FLIPSID_LIB_DIR}/resid")
set(residGenerated "${CMAKE_CURRENT_LIST_DIR}/generated")

if(NOT EXISTS "${residDir}/siddefs.h.in")
    message(FATAL_ERROR
        "reSID: sources not found in ${residDir}.\n"
        "Run lib/fetch_externals.sh (lib\\fetch_externals.cmd on Windows), then configure again.")
endif()

set(residDats
    wave6581__ST wave6581_P_T wave6581_PS_ wave6581_PST
    wave8580__ST wave8580_P_T wave8580_PS_ wave8580_PST
)
set(residHeaders
    sid.h voice.h wave.h envelope.h filter.h filter8580new.h dac.h extfilt.h pot.h spline.h
    resid-config.h
)

# file(READ) doesn't make CMake watch a file, so list everything whose change should rerun
# this file.
set(residWatched
    "${residDir}/siddefs.h.in"
    "${residDir}/configure.in"
    "${residDir}/Makefile.am"
    "${residGenerated}/siddefs.h"
)
foreach(dat IN LISTS residDats)
    list(APPEND residWatched "${residDir}/${dat}.dat")
endforeach()
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${residWatched})

# generated/siddefs.h has to come from the clone's siddefs.h.in. reSID has no version
# number to compare, so the script records the template's hash.
# Hashed as LF text: Git for Windows may check the clone out with CRLF.
file(READ "${residDir}/siddefs.h.in" residTemplate)
string(REPLACE "\r\n" "\n" residTemplate "${residTemplate}")
string(SHA256 residTemplateHash "${residTemplate}")
file(READ "${residGenerated}/siddefs.h" residSiddefs)
if(NOT residSiddefs MATCHES "sha256 ${residTemplateHash}")
    message(FATAL_ERROR
        "reSID: lib/cmake/resid/generated/siddefs.h wasn't made from lib/resid/siddefs.h.in.\n"
        "Run lib/cmake/resid/update_generated.sh and check Makefile.am's source list "
        "against lib/cmake/resid/CMakeLists.txt.")
endif()

# The header decides which filter is built, so the two can't disagree.
if(residSiddefs MATCHES "\n#define NEW_8580_FILTER ([01])\n")
    set(residNew8580Filter ${CMAKE_MATCH_1})
else()
    message(FATAL_ERROR "reSID: no NEW_8580_FILTER in lib/cmake/resid/generated/siddefs.h")
endif()

# AC_INIT([reSID], [1.0-pre2], ...): VERSION goes into resid_version_string.
file(READ "${residDir}/configure.in" residConfigureIn)
if(residConfigureIn MATCHES "AC_INIT\\(\\[reSID\\], \\[([^]]+)\\]")
    set(residVersion "${CMAKE_MATCH_1}")
else()
    message(FATAL_ERROR "reSID: no version in lib/resid/configure.in's AC_INIT")
endif()

# The waveform tables. samp2src.pl writes each .dat byte b as 0x%03x of b << 4 inside
# braces; "0x" + the byte's two hex digits + "0" is the same number.
set(residWaveDir "${CMAKE_CURRENT_BINARY_DIR}/wave")
string(REPEAT "[0-9a-f]" 32 residHexLine)
foreach(dat IN LISTS residDats)
    file(READ "${residDir}/${dat}.dat" hex HEX)
    string(LENGTH "${hex}" hexLength)
    if(NOT hexLength EQUAL 8192)
        message(FATAL_ERROR "reSID: ${dat}.dat should be 4096 bytes")
    endif()
    string(REGEX REPLACE "(${residHexLine})" "\\1\n" hex "${hex}")
    string(REGEX REPLACE "([0-9a-f][0-9a-f])" " 0x\\10," body "${hex}")
    # file(CONFIGURE) leaves an unchanged file alone, so reconfiguring doesn't rebuild.
    file(CONFIGURE OUTPUT "${residWaveDir}/${dat}.h"
        CONTENT
            "// Written by lib/cmake/resid/CMakeLists.txt from lib/resid/${dat}.dat.\n{\n${body}},\n"
        @ONLY)
endforeach()

# reSID's headers under resid/ in the build tree, for users. Copies rather than
# forwarding headers: they include each other and siddefs.h by bare name, and
# siddefs.h only exists here, so a forwarder in front of lib/resid/sid.h would need a
# bare siddefs.h on every user's include path.
set(residInclude "${CMAKE_CURRENT_BINARY_DIR}/include")
foreach(header IN LISTS residHeaders)
    configure_file("${residDir}/${header}" "${residInclude}/resid/${header}" COPYONLY)
endforeach()
configure_file("${residGenerated}/siddefs.h" "${residInclude}/resid/siddefs.h" COPYONLY)
# siddefs.h includes it by bare name.
configure_file("${CMAKE_CURRENT_LIST_DIR}/resid_rand.h" "${residInclude}/resid/resid_rand.h" COPYONLY)

if(residNew8580Filter)
    set(residFilterSource filter8580new.cc)
else()
    set(residFilterSource filter.cc)
endif()

# libresid_a_SOURCES, compiled files only.
set(residSources
    sid.cc
    voice.cc
    wave.cc
    envelope.cc
    ${residFilterSource}
    dac.cc
    extfilt.cc
    pot.cc
    version.cc
)
list(TRANSFORM residSources PREPEND "${residDir}/")
list(APPEND residSources "${CMAKE_CURRENT_LIST_DIR}/resid_rand.cc")

add_library(ext_resid STATIC ${residSources})
add_library(ext::resid ALIAS ext_resid)

target_compile_features(ext_resid PRIVATE cxx_std_20)
set_target_properties(ext_resid PROPERTIES
    OUTPUT_NAME resid
    CXX_EXTENSIONS OFF
    CXX_VISIBILITY_PRESET hidden
    VISIBILITY_INLINES_HIDDEN ON
)

# The .cc files find their own headers next to them; siddefs.h and the waveform tables
# only exist in the build tree.
target_include_directories(ext_resid PRIVATE
    "${residInclude}/resid"
    "${residWaveDir}"
)
target_include_directories(ext_resid SYSTEM INTERFACE "${residInclude}")

# configure adds -DNDEBUG unless --enable-debug. We keep upstream's asserts in Debug, as
# for libresidfp: filter8580new.h checks its table indexes with them on every cycle, which
# is what catches a filter bias pushed out of the model's range.
target_compile_definitions(ext_resid PRIVATE "VERSION=\"${residVersion}\"")

if(MSVC)
    # /w: third-party warnings aren't ours to fix. The sources are plain ASCII.
    target_compile_options(ext_resid PRIVATE /w)
else()
    target_compile_options(ext_resid PRIVATE -w)
endif()
# Not replicated from configure: -fno-exceptions (the library throws nothing itself, but
# std::bad_alloc from new has to reach the builder) and --enable-arch's -march=native,
# which would tie the binary to the build machine's CPU.

if(COMMAND flipsid_configure_third_party)
    flipsid_configure_third_party(ext_resid)
endif()
