cmake_minimum_required(VERSION 3.20)
project(mujofil VERSION 0.1.0 LANGUAGES CXX C)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release)
endif()
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")

# ============================================================================
# Filament (static libs -> baked into the module, nothing to ship separately)
# ============================================================================
# One-command install: if FILAMENT_DIR isn't provided, download the pinned
# Filament release automatically so `pip install mujofil` works from source with
# no manual setup. A prebuilt wheel never hits this path.
set(FILAMENT_VERSION "1.56.3")
if(NOT FILAMENT_DIR)
    set(FILAMENT_DIR "$ENV{FILAMENT_DIR}")
endif()
if(NOT FILAMENT_DIR)
    if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
        set(_fil_os "linux")
    elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
        set(_fil_os "mac")
    elseif(WIN32)
        set(_fil_os "windows")
    else()
        message(FATAL_ERROR "Unsupported platform for Filament auto-download.")
    endif()
    set(FILAMENT_DIR "${CMAKE_BINARY_DIR}/_filament")
    if(NOT EXISTS "${FILAMENT_DIR}/include")
        set(_fil_url "https://github.com/google/filament/releases/download/v${FILAMENT_VERSION}/filament-v${FILAMENT_VERSION}-${_fil_os}.tgz")
        message(STATUS "FILAMENT_DIR not set — downloading Filament v${FILAMENT_VERSION} (${_fil_os})...")
        file(MAKE_DIRECTORY "${FILAMENT_DIR}")
        file(DOWNLOAD "${_fil_url}" "${CMAKE_BINARY_DIR}/filament.tgz" SHOW_PROGRESS STATUS _dl)
        list(GET _dl 0 _dl_rc)
        if(NOT _dl_rc EQUAL 0)
            message(FATAL_ERROR "Failed to download Filament from ${_fil_url} (${_dl}).")
        endif()
        execute_process(
            COMMAND ${CMAKE_COMMAND} -E tar xzf "${CMAKE_BINARY_DIR}/filament.tgz"
            WORKING_DIRECTORY "${FILAMENT_DIR}")
        # The tarball extracts into a 'filament/' subdir — flatten it.
        if(EXISTS "${FILAMENT_DIR}/filament/include")
            file(GLOB _fil_children "${FILAMENT_DIR}/filament/*")
            foreach(_c ${_fil_children})
                get_filename_component(_n "${_c}" NAME)
                file(RENAME "${_c}" "${FILAMENT_DIR}/${_n}")
            endforeach()
        endif()
    endif()
endif()
if(NOT EXISTS "${FILAMENT_DIR}/include")
    message(FATAL_ERROR "FILAMENT_DIR='${FILAMENT_DIR}' has no include/ — bad Filament path.")
endif()
set(FILAMENT_INCLUDE_DIR "${FILAMENT_DIR}/include")
set(FILAMENT_LIB_DIR "${FILAMENT_DIR}/lib/x86_64")

set(FILAMENT_LIBS
    filament backend bluegl bluevk filabridge filaflat utils geometry smol-v
    ibl image camutils filameshio gltfio gltfio_core uberarchive meshoptimizer
    ktxreader basis_transcoder dracodec png z stb uberzlib zstd)

# ============================================================================
# MuJoCo — HEADERS ONLY, and VENDORED into the repo (third_party/mujoco/include).
# The bindings read mjModel/mjData via raw pointers and call no MuJoCo functions,
# so we never link or ship libmujoco. Vendoring the headers (MuJoCo is Apache-2.0)
# means the build needs NO mujoco install at all — identical across every Python
# version and CI container. `mujoco` remains a pure RUNTIME pip dependency that
# provides the actual library when the user runs.
# ============================================================================
if(NOT MUJOCO_INCLUDE_DIR)
    set(MUJOCO_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/third_party/mujoco/include")
endif()
if(NOT EXISTS "${MUJOCO_INCLUDE_DIR}/mujoco/mujoco.h")
    message(FATAL_ERROR "Vendored MuJoCo headers not found at ${MUJOCO_INCLUDE_DIR}.")
endif()
message(STATUS "MuJoCo headers (vendored): ${MUJOCO_INCLUDE_DIR}")

# scikit-build-core sets Python_EXECUTABLE; fall back to a plain interpreter
# lookup for standalone CMake configures.
if(NOT Python_EXECUTABLE)
    find_package(Python COMPONENTS Interpreter REQUIRED)
endif()

find_package(pybind11 CONFIG REQUIRED)
find_package(Threads REQUIRED)

# ============================================================================
# Core static library (the Filament renderer)
# ============================================================================
add_library(vf_mujoco_core STATIC
    src/core/renderer.cpp
    src/core/scene_bridge.cpp
    src/core/material_manager.cpp
    src/core/light_manager.cpp
    src/core/camera_controller.cpp)

target_include_directories(vf_mujoco_core PUBLIC
    ${CMAKE_SOURCE_DIR}/src ${FILAMENT_INCLUDE_DIR} ${MUJOCO_INCLUDE_DIR})
target_link_directories(vf_mujoco_core PUBLIC ${FILAMENT_LIB_DIR})
foreach(lib ${FILAMENT_LIBS})
    target_link_libraries(vf_mujoco_core PUBLIC ${lib})
endforeach()
target_link_libraries(vf_mujoco_core PUBLIC Threads::Threads dl)

# ============================================================================
# Python module
# ============================================================================
pybind11_add_module(_vf_mujoco_native
    src/bindings/bindings.cpp
    src/bindings/renderer_bindings.cpp
    src/bindings/scene_bindings.cpp)
target_link_libraries(_vf_mujoco_native PRIVATE vf_mujoco_core c++ c++abi)
target_include_directories(_vf_mujoco_native PRIVATE
    ${CMAKE_SOURCE_DIR}/src ${FILAMENT_INCLUDE_DIR} ${MUJOCO_INCLUDE_DIR})

# Look for bundled shared libs (libc++ etc., placed by auditwheel) next to the
# module inside the installed package.
set_target_properties(_vf_mujoco_native PROPERTIES
    INSTALL_RPATH "$ORIGIN"
    BUILD_WITH_INSTALL_RPATH ON)

# Install the native module INTO the python package directory.
install(TARGETS _vf_mujoco_native DESTINATION mujofil)

# ============================================================================
# Filament materials, installed as package data.
# Prefer PREBUILT .filamat files committed under mujofil/materials/prebuilt/
# (compiled on a host with a working matc). Filament's matc binary needs glibc
# >= 2.29 and cannot run inside the manylinux_2_28 (glibc 2.28) CI container, so
# we ship the precompiled packages there. If they're absent (e.g. you edited a
# .mat), fall back to compiling with matc.
# ============================================================================
set(PREBUILT_MAT_DIR "${CMAKE_SOURCE_DIR}/mujofil/materials/prebuilt")
file(GLOB PREBUILT_MATERIALS "${PREBUILT_MAT_DIR}/*.filamat")

if(PREBUILT_MATERIALS)
    message(STATUS "Using prebuilt Filament materials from ${PREBUILT_MAT_DIR}")
    install(FILES ${PREBUILT_MATERIALS} DESTINATION mujofil/materials)
    add_custom_target(materials ALL)
else()
    set(MATC "${FILAMENT_DIR}/bin/matc")
    file(GLOB MATERIAL_SOURCES "${CMAKE_SOURCE_DIR}/mujofil/materials/*.mat")
    foreach(mat_file ${MATERIAL_SOURCES})
        get_filename_component(mat_name ${mat_file} NAME_WE)
        set(mat_output "${CMAKE_BINARY_DIR}/materials/${mat_name}.filamat")
        add_custom_command(
            OUTPUT ${mat_output}
            COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/materials"
            COMMAND ${MATC} -o ${mat_output} -a vulkan -a opengl ${mat_file}
            DEPENDS ${mat_file}
            COMMENT "Compiling material: ${mat_name}")
        list(APPEND COMPILED_MATERIALS ${mat_output})
    endforeach()
    add_custom_target(materials ALL DEPENDS ${COMPILED_MATERIALS})
    install(FILES ${COMPILED_MATERIALS} DESTINATION mujofil/materials)
endif()
add_dependencies(_vf_mujoco_native materials)
