include("${CMAKE_CURRENT_LIST_DIR}/version.cmake")

############################## OPTIONS ######################################

set(LV_BUILD_CONF_PATH "" CACHE PATH
    "Use this to specify the location of and/or filename of lv_conf.h")

set(LV_BUILD_CONF_DIR "" CACHE PATH
    "Can be used to specify the include dir containing lv_conf.h, to be used in conjunction with LV_CONF_INCLUDE_SIMPLE")

option(LV_BUILD_USE_KCONFIG "Use Kconfig" OFF)
set(LV_BUILD_DEFCONFIG_PATH "" CACHE PATH
    "Supply the default Kconfig configuration - used with Kconfig")

option(LV_BUILD_SET_CONFIG_OPTS
    "Create variables from the definitions in lv_conf_internal.h"  OFF)

option(LV_BUILD_LVGL_H_SIMPLE_INCLUDE
    "Disable if the lvgl directory is located at the top-level of your project" ON)

option(LV_BUILD_LVGL_H_SYSTEM_INCLUDE
    "Enable if LVGL will be installed on the system or the build system of your application uses a sysroot" OFF)

option(BUILD_SHARED_LIBS "Build shared libraries" OFF)

### LVGL configuration options always use the prefix CONFIG_
### They can be set using the -D argument or cmake-gui(1) and are kept in cache
### The option names are the same as the defines in lv_conf.h - so check it for a description

### INFO: When LV_BUILD_SET_CONFIG_OPTS is enabled - these options are set automatically
### based on lv_conf.h or Kconfig

option(CONFIG_LV_BUILD_DEMOS "Build demos" ON)
option(CONFIG_LV_BUILD_EXAMPLES "Build examples" ON)
option(CONFIG_LV_USE_THORVG_INTERNAL "Use the internal version of ThorVG" ON)
option(CONFIG_LV_USE_PRIVATE_API "If set - install the private headers" OFF)

############################## BUILD ######################################

if (LV_BUILD_CONF_PATH AND LV_BUILD_CONF_DIR)
    message(FATAL_ERROR "can not use LV_BUILD_CONF_DIR and LV_BUILD_CONF_PATH at the same time")
endif()

if (LV_BUILD_USE_KCONFIG)
    # If Kconfig is used then enable PCPP
    set(LV_BUILD_SET_CONFIG_OPTS ON)
endif()

if (LV_BUILD_SET_CONFIG_OPTS)
    # Python is required when LV_BUILD_SET_CONFIG_OPTS is enabled
    # PCPP is used to pre-process lv_conf_internal.h and it avoids setting
    # CONFIG_LV_USE or CONFIG_LV_BUILD options manually
    find_package(Python REQUIRED)
endif()

# Determine if LVGL is built from a top-level directory
get_directory_property(HAS_PARENT_SCOPE PARENT_DIRECTORY)


# Set sources used for LVGL components
file(GLOB_RECURSE SOURCES ${LVGL_ROOT_DIR}/src/*.c
                          ${LVGL_ROOT_DIR}/src/*.cpp
                          ${LVGL_ROOT_DIR}/src/*.S)
file(GLOB_RECURSE EXAMPLE_SOURCES ${LVGL_ROOT_DIR}/examples/*.c)
file(GLOB_RECURSE DEMO_SOURCES ${LVGL_ROOT_DIR}/demos/*.c)
file(GLOB_RECURSE THORVG_SOURCES ${LVGL_ROOT_DIR}/src/libs/thorvg/*.cpp
                                 ${LVGL_ROOT_DIR}/src/others/vg_lite_tvg/*.cpp)

# Check for the case where LVGL is distributed with only its source code
if(NOT EXAMPLE_SOURCES AND CONFIG_LV_BUILD_EXAMPLES)
    message(STATUS "No Examples found. Disabling Examples build")
    set(CONFIG_LV_BUILD_EXAMPLES OFF)
endif()

if(NOT DEMO_SOURCES AND CONFIG_LV_BUILD_DEMOS)
    message(STATUS "No Demos found. Disabling Demos build")
    set(CONFIG_LV_BUILD_DEMOS OFF)
endif()

# Build LVGL library
add_library(lvgl ${SOURCES})
add_library(lvgl::lvgl ALIAS lvgl)

if (NOT LV_BUILD_USE_KCONFIG)

    # Default - use the lv_conf.h configuration file
    target_compile_definitions(lvgl PUBLIC LV_KCONFIG_IGNORE)

    message(STATUS ${LV_BUILD_CONF_PATH})

    if (LV_BUILD_CONF_PATH)

        set(CONF_PATH ${LV_BUILD_CONF_PATH})
        message(STATUS "Using configuration: ${CONF_PATH}")

        if (NOT IS_ABSOLUTE ${CONF_PATH})
            file(REAL_PATH ${CONF_PATH} CONF_PATH BASE_DIRECTORY ${CMAKE_SOURCE_DIR})
            message(STATUS "Converted to absolute path: ${CONF_PATH}")
        endif()

        get_filename_component(CONF_INC_DIR ${CONF_PATH} DIRECTORY)

        target_compile_definitions(lvgl PUBLIC LV_CONF_PATH="${CONF_PATH}")

    elseif(LV_BUILD_CONF_DIR)

        message(STATUS "Using lv_conf.h from the user-supplied configuration directory: ${LV_BUILD_CONF_DIR}")

        set(CONF_INC_DIR ${LV_BUILD_CONF_DIR})

        list(APPEND LVGL_PUBLIC_HEADERS ${CONF_INC_DIR}/lv_conf.h)

        # Avoid compilation errors due to the relative path of the include directory
        if (NOT IS_ABSOLUTE ${CONF_INC_DIR})
            file(REAL_PATH ${CONF_INC_DIR} CONF_INC_DIR BASE_DIRECTORY ${CMAKE_SOURCE_DIR})
            message(STATUS "Converted to absolute path: ${CONF_INC_DIR}")
        endif()

        set(CONF_PATH ${CONF_INC_DIR}/lv_conf.h)

        target_compile_definitions(lvgl PUBLIC LV_CONF_INCLUDE_SIMPLE)

    else()

        message(STATUS "Using lv_conf.h from the top-level project directory")

        list(APPEND LVGL_PUBLIC_HEADERS ${CMAKE_SOURCE_DIR}/lv_conf.h)

        set(CONF_INC_DIR ${CMAKE_SOURCE_DIR})
        set(CONF_PATH ${CONF_INC_DIR}/lv_conf.h)

        target_compile_definitions(lvgl PUBLIC LV_CONF_INCLUDE_SIMPLE)

    endif()

    # Check if lv_conf.h exists at the configured location
    if (NOT EXISTS ${CONF_PATH})
        message(FATAL_ERROR "Configuration file: ${CONF_PATH} - not found")
    endif()

else()

    # Use kconfig
    # kconfig.cmake will generate the .config
    # and autoconf.h, which will be used by lv_conf_kconfig.h
    include(${CMAKE_CURRENT_LIST_DIR}/kconfig.cmake)

    # Set the flag to specify we are using kconfig, needed for the
    # generate_cmake_variables.py script.
    set(GEN_VARS_KCONFIG_MODE_FLAG --kconfig)

    # If using Kconfig, we need to define additional definitions
    target_compile_definitions(lvgl PUBLIC
        "LV_CONF_SKIP"
        "LV_CONF_KCONFIG_EXTERNAL_INCLUDE=\"${KCONFIG_EXTERNAL_INCLUDE}\"")

endif()

if (LV_BUILD_LVGL_H_SYSTEM_INCLUDE)
    target_compile_definitions(lvgl PUBLIC LV_LVGL_H_INCLUDE_SYSTEM)
elseif(LV_BUILD_LVGL_H_SIMPLE_INCLUDE)
    target_compile_definitions(lvgl PUBLIC LV_LVGL_H_INCLUDE_SIMPLE)
endif()


if (LV_BUILD_SET_CONFIG_OPTS)
    # Use the portable pcpp to preprocess lv_conf_internal.h

    get_target_property(CONF_DEFINES lvgl COMPILE_DEFINITIONS)

    execute_process(
        COMMAND ${Python_EXECUTABLE} ${LVGL_ROOT_DIR}/scripts/preprocess_lv_conf_internal.py
        --input ${LVGL_ROOT_DIR}/src/lv_conf_internal.h
        --tmp_file ${CMAKE_CURRENT_BINARY_DIR}/tmp.h
        --output ${CMAKE_CURRENT_BINARY_DIR}/lv_conf_expanded.h
        --workfolder ${CMAKE_CURRENT_BINARY_DIR}
        --defs ${CONF_DEFINES}
        --include ${LVGL_ROOT_DIR} ${CMAKE_SOURCE_DIR} ${LVGL_ROOT_DIR}/src ${CONF_INC_DIR}
        RESULT_VARIABLE ret
    )
    if(NOT "${ret}" STREQUAL "0")
        message(FATAL_ERROR "preprocess_lv_conf_internal.py failed with return code: ${ret}")
    endif()

    if(HAS_PARENT_SCOPE)
        # Also set the CONFIG_LV_USE_* and CONFIG_LV_BUILD_* configuration
        # on the parent scope
        set(PARENT_SCOPE_ARG "--parentscope")
    else()
        set(PARENT_SCOPE_ARG "")
    endif()

    # Convert the expanded lv_conf_expanded.h to cmake variables
    execute_process(
        COMMAND ${Python_EXECUTABLE}
        ${LVGL_ROOT_DIR}/scripts/generate_cmake_variables.py
        --input ${CMAKE_CURRENT_BINARY_DIR}/lv_conf_expanded.h
        --output ${CMAKE_CURRENT_BINARY_DIR}/lv_conf.cmake
        ${PARENT_SCOPE_ARG}
        ${GEN_VARS_KCONFIG_MODE_FLAG}
        RESULT_VARIABLE ret
    )

    if(NOT "${ret}" STREQUAL "0")
        message(FATAL_ERROR "generate_cmake_variables.py command failed with return code: ${ret}")
    endif()

    # This will set all CONFIG_LV_USE_* or CONFIG_LV_BUILD_* variables in cmake
    include(${CMAKE_CURRENT_BINARY_DIR}/lv_conf.cmake)
endif()

# Set the configuration inc dir for all targets created in this CMakeLists.txt
# CMAKE_CURRENT_SOURCE_DIR is necessary because the assets include lvgl/lvgl.h ...
include_directories(${CONF_INC_DIR} ${LVGL_ROOT_DIR})

target_include_directories(lvgl SYSTEM PUBLIC ${LVGL_ROOT_DIR} ${CONF_INC_DIR} ${CMAKE_CURRENT_BINARY_DIR})

# Propagate the compiler definitions set on LVGL to the rest of the targets
# mentioned in this file
get_target_property(COMP_DEF lvgl COMPILE_DEFINITIONS)

if(CONFIG_LV_USE_THORVG_INTERNAL)
    message(STATUS "Enabling the building of ThorVG internal")

    add_library(lvgl_thorvg ${THORVG_SOURCES})
    add_library(lvgl::thorvg ALIAS lvgl_thorvg)
    target_include_directories(lvgl_thorvg PRIVATE ${LVGL_ROOT_DIR}/src/libs/thorvg)
    set_target_properties(lvgl_thorvg PROPERTIES COMPILE_DEFINITIONS "${COMP_DEF}")

    # This tells cmake to link lvgl with lvgl_thorvg
    # The linker will resolve all dependencies when dynamic linking 
    target_link_libraries(lvgl PRIVATE lvgl_thorvg)
    # During static linking, we need to create a cyclic dependency as thorvg also needs lvgl
    if (NOT BUILD_SHARED_LIBS)
        target_link_libraries(lvgl_thorvg PRIVATE lvgl)
    endif()
endif()

if(NOT (CMAKE_C_COMPILER_ID STREQUAL "MSVC"))
    set_source_files_properties(${LVGL_ROOT_DIR}/src/others/vg_lite_tvg/vg_lite_tvg.cpp
        PROPERTIES COMPILE_FLAGS -Wunused-parameter)
endif()

# Build LVGL example library
if(CONFIG_LV_BUILD_EXAMPLES)

    message(STATUS "Enabling the building of examples")

    add_library(lvgl_examples ${EXAMPLE_SOURCES})
    add_library(lvgl::examples ALIAS lvgl_examples)
    target_include_directories(lvgl_examples SYSTEM PUBLIC ${LVGL_ROOT_DIR}/examples)
    set_target_properties(lvgl_examples PROPERTIES COMPILE_DEFINITIONS "${COMP_DEF}")

    # This tells cmake to link lvgl with lvgl_examples
    # PUBLIC allows code linking with LVGL to also use the library
    # The linker will resolve all dependencies when dynamic linking 
    target_link_libraries(lvgl PUBLIC lvgl_examples)

    # During static linking, we need to create a cyclic dependency as the examples also needs lvgl
    if (NOT BUILD_SHARED_LIBS)
        target_link_libraries(lvgl_examples PRIVATE lvgl)
    endif()

endif()

# Build LVGL demos library
if(CONFIG_LV_BUILD_DEMOS)

    message(STATUS "Enabling the building of demos")

    add_library(lvgl_demos ${DEMO_SOURCES})
    add_library(lvgl::demos ALIAS lvgl_demos)
    target_include_directories(lvgl_demos SYSTEM PUBLIC ${LVGL_ROOT_DIR}/demos)
    set_target_properties(lvgl_demos PROPERTIES COMPILE_DEFINITIONS "${COMP_DEF}")

    # This tells cmake to link lvgl with lvgl_examples
    # PUBLIC allows code linking with LVGL to also use the library
    # The linker will resolve all dependencies when dynamic linking 
    target_link_libraries(lvgl PUBLIC lvgl_demos)

    # During static linking, we need to create a cyclic dependency as the demos also needs lvgl
    if (NOT BUILD_SHARED_LIBS)
        # If static linking - demos depends on fonts defined in lvgl
        # During dynamic linking, the linker is able to resolve everything
        target_link_libraries(lvgl_demos PRIVATE lvgl)
    endif()

endif()

############################## INSTALLATION ######################################

# Library and headers can be installed to system using make install
file(GLOB LVGL_PUBLIC_HEADERS
    "${LVGL_ROOT_DIR}/lvgl.h"
    "${CONF_INC_DIR}/*.h"
    "${LVGL_ROOT_DIR}/lv_version.h")

if (CONFIG_LV_USE_PRIVATE_API)
    list(APPEND LVGL_PUBLIC_HEADERS "${LVGL_ROOT_DIR}/lvgl_private.h")
endif()

if("${LIB_INSTALL_DIR}" STREQUAL "")
    set(LIB_INSTALL_DIR "lib")
endif()
if("${RUNTIME_INSTALL_DIR}" STREQUAL "")
    set(RUNTIME_INSTALL_DIR "bin")
endif()
if("${INC_INSTALL_DIR}" STREQUAL "")
    set(INC_INSTALL_DIR "include/lvgl")
endif()

#Install public headers
install(
    DIRECTORY "${LVGL_ROOT_DIR}/src"
    DESTINATION "${INC_INSTALL_DIR}"
    FILES_MATCHING
    PATTERN "*.h"
    PATTERN "*_private.h" EXCLUDE)

if (CONFIG_LV_USE_PRIVATE_API)
    # Install private headers - only if required
    install(
        DIRECTORY "${LVGL_ROOT_DIR}/src"
        DESTINATION "${INC_INSTALL_DIR}"
        FILES_MATCHING
        PATTERN "*_private.h")
endif()


if (LV_BUILD_USE_KCONFIG)
    # When KConfig is used, copy the expanded conf header
    # and rename it to lv_conf.h
    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/lv_conf_expanded.h
            DESTINATION ${INC_INSTALL_DIR}
            RENAME lv_conf.h)
endif()

install(
    FILES ${LVGL_PUBLIC_HEADERS}
    DESTINATION "${INC_INSTALL_DIR}/")

# Install library
set_target_properties(
    lvgl
    PROPERTIES OUTPUT_NAME lvgl
    VERSION ${LVGL_VERSION}
    SOVERSION ${LVGL_SOVERSION}
    ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/lib"
    LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/lib"
    PUBLIC_HEADER "${LVGL_PUBLIC_HEADERS}")

install(
    TARGETS lvgl
    ARCHIVE DESTINATION "${LIB_INSTALL_DIR}"
    LIBRARY DESTINATION "${LIB_INSTALL_DIR}"
    RUNTIME DESTINATION "${RUNTIME_INSTALL_DIR}"
    PUBLIC_HEADER DESTINATION "${INC_INSTALL_DIR}")

# TODO: if LVGL is linked with something else eg Freetype, Pkgconfig file must contain -lfreetype2
configure_file("${LVGL_ROOT_DIR}/lvgl.pc.in" ${CMAKE_CURRENT_BINARY_DIR}/lvgl.pc @ONLY)
configure_file("${LVGL_ROOT_DIR}/lv_version.h.in" ${CMAKE_CURRENT_BINARY_DIR}/lv_version.h @ONLY)

install(
    FILES "${CMAKE_CURRENT_BINARY_DIR}/lvgl.pc"
    DESTINATION "share/pkgconfig/")

# Install library thorvg
if(CONFIG_LV_USE_THORVG_INTERNAL)

    set_target_properties(
        lvgl_thorvg
        PROPERTIES OUTPUT_NAME lvgl_thorvg
        VERSION ${LVGL_VERSION}
        SOVERSION ${LVGL_SOVERSION}
        ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/lib"
        LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/lib"
        PUBLIC_HEADER "${LVGL_PUBLIC_HEADERS}")

    install(
        TARGETS lvgl_thorvg
        ARCHIVE DESTINATION "${LIB_INSTALL_DIR}"
        LIBRARY DESTINATION "${LIB_INSTALL_DIR}"
        RUNTIME DESTINATION "${RUNTIME_INSTALL_DIR}"
        PUBLIC_HEADER DESTINATION "${INC_INSTALL_DIR}")

endif()

if(CONFIG_LV_BUILD_DEMOS)

    install(
        DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/demos"
        DESTINATION "${INC_INSTALL_DIR}"
        FILES_MATCHING
        PATTERN "*.h")

    set_target_properties(
        lvgl_demos
        PROPERTIES OUTPUT_NAME lvgl_demos
        VERSION ${LVGL_VERSION}
        SOVERSION ${LVGL_SOVERSION}
        ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/lib"
        LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/lib"
        PUBLIC_HEADER "${LVGL_PUBLIC_HEADERS}")

    install(
        TARGETS lvgl_demos
        ARCHIVE DESTINATION "${LIB_INSTALL_DIR}"
        LIBRARY DESTINATION "${LIB_INSTALL_DIR}"
        RUNTIME DESTINATION "${RUNTIME_INSTALL_DIR}"
        PUBLIC_HEADER DESTINATION "${INC_INSTALL_DIR}")

endif()

if(CONFIG_LV_BUILD_EXAMPLES)

    install(
        DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/examples"
        DESTINATION "${INC_INSTALL_DIR}"
        FILES_MATCHING
        PATTERN "*.h")

    set_target_properties(
        lvgl_examples
        PROPERTIES OUTPUT_NAME lvgl_examples
        VERSION ${LVGL_VERSION}
        SOVERSION ${LVGL_SOVERSION}
        ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/lib"
        LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/lib"
        PUBLIC_HEADER "${LVGL_PUBLIC_HEADERS}")

    install(
        TARGETS lvgl_examples
        ARCHIVE DESTINATION "${LIB_INSTALL_DIR}"
        LIBRARY DESTINATION "${LIB_INSTALL_DIR}"
        RUNTIME DESTINATION "${RUNTIME_INSTALL_DIR}"
        PUBLIC_HEADER DESTINATION "${INC_INSTALL_DIR}")

endif()

############################## SET GLOBAL VARIABLES ######################################

if (HAS_PARENT_SCOPE)
    # Set the configuration include directory on the top-level project
    # Useful so that it doesn't need to be determined a second time in the project itself
    set(LVGL_CONF_INC_DIR ${CONF_INC_DIR} PARENT_SCOPE)
    set(LVGL_CONF_PATH ${CONF_PATH} PARENT_SCOPE)
    set(LVGL_COMPILER_DEFINES ${COMP_DEF} PARENT_SCOPE)

else()
    # The file has been included by tests/CMakeLists.txt -
    # or somewhere else, set variable containing the normalized path of the include dir
    # containing the configuration file
    set(LVGL_CONF_INC_DIR ${CONF_INC_DIR})
    set(LVGL_CONF_PATH ${CONF_PATH})
    set(LVGL_COMPILER_DEFINES ${COMP_DEF})
endif()
