# Check if python wrapper should be built
if (NOT MCDS_BUILD_PYTHON)
    return()
endif()

# -----------------------------------------------------------------------------
# Python project definition
# -----------------------------------------------------------------------------
# Check if python wrapper should be built
if (NOT MCDS_BUILD_PYTHON)
    return()
endif()

# -----------------------------------------------------------------------------
# Python project definition
# -----------------------------------------------------------------------------
set(PYTHON_PROJECT pymcds)
set(PYTHON_PROJECT_VERSION 0.4.0)
set(PYTHON_PROJECT_DESCRIPTION "Python wrapper for the MCDS libraires")
message(STATUS "Python project: ${PYTHON_PROJECT}")

set(PYTHON_PROJECT_DIR ${PROJECT_BINARY_DIR}/python/src/${PYTHON_PROJECT})
message(STATUS "Python project build path: ${PYTHON_PROJECT_DIR}")

# These are pymcds package dependencies
set(DEPENDENCIES_LIST "numpy")

set(PYTHON_PROJECT_DEPENDENCIES "")
foreach(ITEM IN LISTS DEPENDENCIES_LIST)
    # Append the quoted item to the new list
    list(APPEND PYTHON_PROJECT_DEPENDENCIES "\"${ITEM}\"")
endforeach(ITEM IN LISTS )

string(REPLACE ";" "," PYTHON_PROJECT_DEPENDENCIES "${PYTHON_PROJECT_DEPENDENCIES}")

# -----------------------------------------------------------------------------
# Find relevant dependencies
# -----------------------------------------------------------------------------
# Uncomment and spcify the root of Python installation if specific version should be used, otherwise the system default 
# is selected 
if (WIN32)
    #set(Python3_ROOT_DIR "C:\\Program Files\\Python311")
elseif (UNIX)
    #set(Python3_ROOT_DIR "/usr/bin/python3.11")
endif()

set(PYBIND11_FINDPYTHON ON)
find_package(pybind11 CONFIG REQUIRED)

if (NOT pybind11_FOUND)
    message(FATAL_ERROR "pybind11 not found ${pybind11_FOUND}. Dependecies managed by conan, run 'conan install .' first")
endif()

message(STATUS "Python executable found by pybind11: ${Python_EXECUTABLE}")

# ================================ General configuration ======================================
# Solution for an open issue due to installed python debug libs: https://github.com/pybind/pybind11/issues/3403
set_target_properties(Python::Module PROPERTIES
        MAP_IMPORTED_CONFIG_DEBUG ";RELEASE")

# Setup virtual pyton environment for testing and stub gen
# Setup variables for targeting a virtual environment
set(VENV_DIR "${CMAKE_CURRENT_BINARY_DIR}/pymcds_venv")
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
    set(VENV_BIN "${VENV_DIR}/Scripts")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR CMAKE_SYSTEM_NAME STREQUAL "Linux")
    set(VENV_BIN "${VENV_DIR}/bin")
else()
    message(FATAL_ERROR "Unsupported platform: ${CMAKE_SYSTEM_NAME}")
endif()

# Create virtual environment
if(NOT EXISTS ${VENV_DIR})
    message(STATUS "Creating virtual environment...")
    execute_process(
        COMMAND ${Python_EXECUTABLE} -m venv ${VENV_DIR}
        WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
    )


    cmake_path(GET Python_EXECUTABLE FILENAME Python_EXECUTABLE_NAME)
    find_program(Python_VENV_EXECUTABLE NAMES ${Python_EXECUTABLE_NAME} PATHS ${VENV_BIN} NO_CACHE NO_DEFAULT_PATH)
    message(STATUS "Python executable in venv: ${Python_VENV_EXECUTABLE}")

    if(NOT Python_VENV_EXECUTABLE)
        message(FATAL_ERROR "Python executable not found in virtual environment: ${Python_VENV_EXECUTABLE}")
    endif()

    # Windows-specific: run activate.bat before pip commands
    if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
        execute_process(
            COMMAND cmd /c "${VENV_DIR}\\Scripts\\activate && ${Python_VENV_EXECUTABLE} -m pip install --upgrade pip --trusted-host artifactory.intra.infineon.com"
            RESULT_VARIABLE PIP_UPGRADE_RESULT
            ERROR_VARIABLE PIP_UPGRADE_ERROR
            OUTPUT_VARIABLE PIP_UPGRADE_OUTPUT
            WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
        )
    else()
        # macOS/Linux: source activate and run pip commands
        execute_process(
            COMMAND /bin/bash -c "source ${VENV_DIR}/bin/activate && ${Python_VENV_EXECUTABLE} -m pip install --upgrade pip --trusted-host artifactory.intra.infineon.com"
            RESULT_VARIABLE PIP_UPGRADE_RESULT
            ERROR_VARIABLE PIP_UPGRADE_ERROR
            OUTPUT_VARIABLE PIP_UPGRADE_OUTPUT
            WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
        )
    endif()

    if(NOT PIP_UPGRADE_RESULT EQUAL 0)
        message(FATAL_ERROR "Failed to upgrade pip. See error: ${PIP_UPGRADE_ERROR}")
    endif()

    # Install required Python packages
    message(STATUS "Venv exe: ${Python_VENV_EXECUTABLE}")
    execute_process(
        COMMAND ${Python_VENV_EXECUTABLE} -m pip install pip-system-certs --trusted-host artifactory.intra.infineon.com 
        COMMAND ${Python_VENV_EXECUTABLE} -m pip install -r ${CMAKE_CURRENT_SOURCE_DIR}/requirements.txt --trusted-host artifactory.intra.infineon.com 
        WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
    )

endif()

cmake_path(GET Python_EXECUTABLE FILENAME Python_EXECUTABLE_NAME)
find_program(Python_VENV_EXECUTABLE NAMES ${Python_EXECUTABLE_NAME} PATHS ${VENV_BIN} NO_CACHE NO_DEFAULT_PATH)
message(STATUS "Python executable in venv: ${Python_VENV_EXECUTABLE}")

if(NOT Python_VENV_EXECUTABLE)
    message(FATAL_ERROR "Python executable not found in virtual environment: ${Python_VENV_EXECUTABLE}")
endif()

# =============================== CMake target - bindings_library =============================
# Add the pymcds_ifx_devices_tools module
pybind11_add_module(pymcds_ifx_devices_tools
    bindings/mcds_ifx_devices_tools_binding.cpp
    "${CMAKE_SOURCE_DIR}/include/mcds_libs/mcds_ifx_devices_tools.h"
)

target_include_directories(pymcds_ifx_devices_tools 
    PRIVATE         
        "${pybind11_INCLUDE_DIR}"
        "${CMAKE_SOURCE_DIR}/include/mcds_libs"
        "${CMAKE_SOURCE_DIR}/src"   
)
# -----------------------------------------------------------------------------

# Add the pymcds_lib_support module
pybind11_add_module(pymcds_lib_support
    bindings/mcds_lib_support_binding.cpp
    "${CMAKE_SOURCE_DIR}/include/mcds_libs/mcds_lib.h"
    "${CMAKE_CURRENT_SOURCE_DIR}/bindings/mcds_python_helpers.h"
    "${CMAKE_CURRENT_SOURCE_DIR}/bindings/mcds_binding_utils.h"
)

target_include_directories(pymcds_lib_support 
    PRIVATE 
        "${pybind11_INCLUDE_DIR}"
        "${CMAKE_SOURCE_DIR}/include/mcds_libs"
        "${CMAKE_SOURCE_DIR}/src"
        "${CMAKE_CURRENT_SOURCE_DIR}/bindings"
)
# -----------------------------------------------------------------------------

# Add the pymcds_control module
pybind11_add_module(pymcds_control 
    "${CMAKE_CURRENT_SOURCE_DIR}/bindings/mcds_control_binding.cpp"
    "${CMAKE_CURRENT_SOURCE_DIR}/bindings/mcds_binding_utils.cpp"
    "${CMAKE_SOURCE_DIR}/src/mcds/mcds_control.cpp"
)

target_include_directories(pymcds_control 
    PRIVATE 
        "${pybind11_INCLUDE_DIR}"
        "${CMAKE_SOURCE_DIR}/include/mcds_libs"
        "${CMAKE_SOURCE_DIR}/src"
        "${CMAKE_CURRENT_SOURCE_DIR}/bindings"
)
add_dependencies(pymcds_control pymcds_ifx_devices_tools)
# -----------------------------------------------------------------------------

# Add the mcd_types module
pybind11_add_module(mcd_types 
    bindings/mcd_types_binding.cpp
)

target_include_directories(mcd_types 
    PRIVATE 
        "${pybind11_INCLUDE_DIR}"
        "${CMAKE_SOURCE_DIR}/include/mcds_libs"
        "${CMAKE_SOURCE_DIR}/src"
)
# -----------------------------------------------------------------------------

# Add the pymcds_configurator module
pybind11_add_module(pymcds_configurator 
    "${CMAKE_CURRENT_SOURCE_DIR}/bindings/mcds_configurator_binding.cpp"
    "${CMAKE_CURRENT_SOURCE_DIR}/bindings/mcds_binding_utils.h"
    "${CMAKE_CURRENT_SOURCE_DIR}/bindings/mcds_binding_utils.cpp"
    "${CMAKE_SOURCE_DIR}/src/mcds/mcds_control.cpp"
)

target_include_directories(pymcds_configurator 
    PRIVATE 
        "${pybind11_INCLUDE_DIR}"
        "${CMAKE_SOURCE_DIR}/include/mcds_libs"
        "${CMAKE_SOURCE_DIR}/src"
        "${CMAKE_CURRENT_SOURCE_DIR}/bindings"
        "${CMAKE_BINARY_DIR}"
)

target_link_libraries(pymcds_configurator PRIVATE mcds_configurator)
add_dependencies(pymcds_configurator mcd_types pymcds_control pymcds_lib_support mcds_configurator)
# -----------------------------------------------------------------------------

# Add the recorder module
pybind11_add_module(pymcds_recorder 
    bindings/mcds_recorder_binding.cpp
    "${CMAKE_SOURCE_DIR}/src/mcds_lib/mcds_frequency_measurement.cpp"
    "${CMAKE_SOURCE_DIR}/src/mcds/mcds_control.cpp"
)

target_include_directories(pymcds_recorder 
    PRIVATE 
        "${pybind11_INCLUDE_DIR}"
        "${tas_client_api_INCLUDE_DIRS}"
        "${CMAKE_SOURCE_DIR}/include/mcds_libs"
        "${CMAKE_SOURCE_DIR}/src"
        "${CMAKE_CURRENT_SOURCE_DIR}/bindings/mcds_python_helpers.h"
        "${CMAKE_SOURCE_DIR}/src/mcds_lib" 
        
)

target_link_libraries(pymcds_recorder PRIVATE mcds_recorder tas_client_api::tas_client)
add_dependencies(pymcds_recorder mcd_types pymcds_control pymcds_lib_support mcds_recorder)
# -----------------------------------------------------------------------------

# Add the decoder module
pybind11_add_module(pymcds_decoder
    bindings/mcds_decoder_binding.cpp
)

target_include_directories(pymcds_decoder
    PRIVATE 
        "${pybind11_INCLUDE_DIR}"
        "${CMAKE_SOURCE_DIR}/include/mcds_libs"
        "${CMAKE_SOURCE_DIR}/src"
        "${CMAKE_CURRENT_SOURCE_DIR}/bindings/mcds_python_helpers.h"
)

target_link_libraries(pymcds_decoder PRIVATE mcds_decoder)
add_dependencies(pymcds_decoder mcd_types pymcds_control pymcds_lib_support mcds_decoder)
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Python package
# -----------------------------------------------------------------------------
# pyproject.toml.in contains cmake variable e.g. @PYTHON_PROJECT@ and
# generator expression e.g. $<TARGET_FILE_NAME:pyFoo>
configure_file(
    ${PROJECT_SOURCE_DIR}/cmake/templates/pyproject.toml.in
    ${PROJECT_BINARY_DIR}/python/pyproject.toml.in
    @ONLY
)
# -----------------------------------------------------------------------------

# This step is required to trigger the generator expressions
file(GENERATE
    OUTPUT ${PROJECT_BINARY_DIR}/python/pyproject_tmp.toml
    INPUT ${PROJECT_BINARY_DIR}/python/pyproject.toml.in
)

# Add a custom command to run the Python script after generating pyproject.toml
add_custom_command(
    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/pyproject.toml  # Output file
    COMMAND ${Python_VENV_EXECUTABLE} ${PROJECT_SOURCE_DIR}/tools/update_python_package_version.py ${PROJECT_BINARY_DIR}/python/pyproject_tmp.toml
    COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_BINARY_DIR}/python/pyproject_tmp.toml ${PROJECT_BINARY_DIR}/python/pyproject.toml
    DEPENDS 
        ${PROJECT_BINARY_DIR}/python/pyproject_tmp.toml  # Dependency file
    COMMENT "Running update_python_package_version.py to add dev suffix to version"
    VERBATIM
    WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
)

# Ensure that the custom command is executed as part of the build
add_custom_target(run_update_version ALL DEPENDS ${PROJECT_BINARY_DIR}/python/pyproject.toml)
# -----------------------------------------------------------------------------

# Prepare the python package layout in the ${PYTHON_PROJECT_DIR} directory 
add_custom_command(
    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/copy_done

    COMMAND ${CMAKE_COMMAND} -E remove_directory ${PYTHON_PROJECT_DIR}
   
    COMMAND ${CMAKE_COMMAND} -E make_directory ${PYTHON_PROJECT_DIR}
    COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:pymcds_ifx_devices_tools> ${PYTHON_PROJECT_DIR}
    COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:pymcds_lib_support> ${PYTHON_PROJECT_DIR}
    COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:pymcds_control> ${PYTHON_PROJECT_DIR}
    COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:mcd_types> ${PYTHON_PROJECT_DIR}
    COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:pymcds_configurator> ${PYTHON_PROJECT_DIR}
    COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:pymcds_recorder> ${PYTHON_PROJECT_DIR}
    COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:pymcds_decoder> ${PYTHON_PROJECT_DIR}
    COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:mcds_configurator> ${PYTHON_PROJECT_DIR}
    COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:mcds_decoder> ${PYTHON_PROJECT_DIR}
    COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:mcds_recorder> ${PYTHON_PROJECT_DIR}
    COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/LICENSE ${PROJECT_BINARY_DIR}/python
    COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/README.md ${PROJECT_BINARY_DIR}/python
    COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/setup.py ${PROJECT_BINARY_DIR}/python
    COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/src/pymcds/__init__.py ${PYTHON_PROJECT_DIR}
    COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/src/pymcds/mcds_lib.py ${PYTHON_PROJECT_DIR}

    COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/copy_done

    DEPENDS
        pymcds_ifx_devices_tools
        pymcds_lib_support
        pymcds_control
        mcd_types
        pymcds_configurator
        pymcds_recorder
        pymcds_decoder
        mcds_configurator
        mcds_decoder
        mcds_recorder
        ${CMAKE_CURRENT_SOURCE_DIR}/src/pymcds/__init__.py
        ${CMAKE_CURRENT_SOURCE_DIR}/src/pymcds/mcds_lib.py
    
    BYPRODUCTS
        ${PYTHON_PROJECT_DIR}/__init__.py
        ${PYTHON_PROJECT_DIR}/mcds_lib.py

    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
    COMMAND_EXPAND_LISTS
    VERBATIM
)
# -----------------------------------------------------------------------------

# Stub file generation
add_custom_command(
    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/stub_gen_done

    COMMAND ${Python_VENV_EXECUTABLE} -m pybind11_stubgen -o ${CMAKE_CURRENT_BINARY_DIR}/src pymcds.pymcds_ifx_devices_tools 
    COMMAND ${Python_VENV_EXECUTABLE} -m pybind11_stubgen -o ${CMAKE_CURRENT_BINARY_DIR}/src pymcds.pymcds_lib_support 
    COMMAND ${Python_VENV_EXECUTABLE} -m pybind11_stubgen -o ${CMAKE_CURRENT_BINARY_DIR}/src pymcds.pymcds_control 
    COMMAND ${Python_VENV_EXECUTABLE} -m pybind11_stubgen -o ${CMAKE_CURRENT_BINARY_DIR}/src pymcds.mcd_types
    COMMAND ${Python_VENV_EXECUTABLE} -m pybind11_stubgen -o ${CMAKE_CURRENT_BINARY_DIR}/src pymcds.pymcds_configurator 
    COMMAND ${Python_VENV_EXECUTABLE} -m pybind11_stubgen -o ${CMAKE_CURRENT_BINARY_DIR}/src pymcds.pymcds_decoder --enum-class-locations mcdsdl_msg_str_option_et:pymcds.pymcds_decoder.mcdsdl_msg_str_option_et
    COMMAND ${Python_VENV_EXECUTABLE} -m pybind11_stubgen -o ${CMAKE_CURRENT_BINARY_DIR}/src pymcds.pymcds_recorder
    COMMAND ${Python_VENV_EXECUTABLE} -m pybind11_stubgen -o ${CMAKE_CURRENT_BINARY_DIR}/src pymcds.mcds_lib
    COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/stub_gen_done

    DEPENDS 
        ${CMAKE_CURRENT_BINARY_DIR}/copy_done

    BYPRODUCTS
        ${PYTHON_PROJECT_DIR}/pymcds_ifx_devices_tools.pyi
        ${PYTHON_PROJECT_DIR}/pymcds_lib_support.pyi
        ${PYTHON_PROJECT_DIR}/pymcds_control.pyi
        ${PYTHON_PROJECT_DIR}/mcd_types.pyi
        ${PYTHON_PROJECT_DIR}/pymcds_configurator.pyi
        ${PYTHON_PROJECT_DIR}/pymcds_decoder.pyi
        ${PYTHON_PROJECT_DIR}/pymcds_recorder.pyi
        ${PYTHON_PROJECT_DIR}/mcds_lib.pyi

    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/src
    VERBATIM
)
# -----------------------------------------------------------------------------

# Python package generation
add_custom_command(
    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/python_package_created

    COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_CURRENT_BINARY_DIR}/dist
    COMMAND ${Python_VENV_EXECUTABLE} -m build
    COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/python_package_created

    DEPENDS 
        ${CMAKE_CURRENT_BINARY_DIR}/stub_gen_done

    BYPRODUCTS
        ${PYTHON_PROJECT}
        ${CMAKE_CURRENT_BINARY_DIR}/src/${PYTHON_PROJECT}.egg-info
        ${CMAKE_CURRENT_BINARY_DIR}/dist

    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
    VERBATIM
)
# -----------------------------------------------------------------------------

add_custom_target(python_package ALL
    DEPENDS 
        ${CMAKE_CURRENT_BINARY_DIR}/python_package_created
        ${CMAKE_CURRENT_BINARY_DIR}/pyproject.toml
    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Python tests
# -----------------------------------------------------------------------------
# Check if the Python executable exists
if(NOT Python_VENV_EXECUTABLE)
    message(FATAL_ERROR "Python executable not found in virtual environment.")
endif()

# Install the .whl file using a custom script to ensure platform independence
file(WRITE ${CMAKE_BINARY_DIR}/install_whl.py
"import os
import subprocess
import glob

whl_dir = os.path.join('${CMAKE_CURRENT_BINARY_DIR}', 'dist')
whl_files = glob.glob(os.path.join(whl_dir, '*.whl'))

if whl_files:
    for whl_file in whl_files:
        print(f'Installing {whl_file}')
        subprocess.check_call(['${Python_VENV_EXECUTABLE}', '-m', 'pip', 'install', '--force-reinstall', '--no-deps', '--upgrade', whl_file])
else:
    raise FileNotFoundError('No .whl file found in the specified directory')
")

add_custom_command(TARGET python_package POST_BUILD
    COMMAND ${Python_VENV_EXECUTABLE} ${CMAKE_BINARY_DIR}/install_whl.py
    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
    COMMENT "Installing ${PYTHON_PROJECT}"
    VERBATIM
)

# add_python_test()
# CMake function to generate and build python test.
# Parameters:
#  the python filename
# e.g.:
# add_python_test(foo.py)
function(add_python_test FILE_NAME)
    message(STATUS "Configuring test ${FILE_NAME} ...")
    get_filename_component(EXAMPLE_NAME ${FILE_NAME} NAME_WE)

    if(MCDS_BUILD_TEST)
        add_test(
            NAME python_${EXAMPLE_NAME}
            COMMAND ${Python_VENV_EXECUTABLE} ${FILE_NAME}
            WORKING_DIRECTORY ${VENV_DIR})
    endif()
    message(STATUS "Configuring test ${FILE_NAME} done")
endfunction()

add_python_test(${CMAKE_CURRENT_SOURCE_DIR}/tests/test_pymcds_ifx_devices_tools.py)
add_python_test(${CMAKE_CURRENT_SOURCE_DIR}/tests/test_pymcds_lib_support.py)
add_python_test(${CMAKE_CURRENT_SOURCE_DIR}/tests/test_pymcds_control.py)
add_python_test(${CMAKE_CURRENT_SOURCE_DIR}/tests/test_mcd_types.py)
add_python_test(${CMAKE_CURRENT_SOURCE_DIR}/tests/test_pymcds_configurator.py)
add_python_test(${CMAKE_CURRENT_SOURCE_DIR}/tests/test_pymcds_recorder.py)
add_python_test(${CMAKE_CURRENT_SOURCE_DIR}/tests/test_pymcds_decoder.py)
