cmake_minimum_required(VERSION 3.14)

# ================= Configuration =================

set(LVED_PROJECT_NAME
    "lved-runtime"
    CACHE STRING "Project name")

project(${LVED_PROJECT_NAME} LANGUAGES C)

set(LVED_ENV
    "web"
    CACHE STRING "Specify environment")

set(LVED_LVGL_VERSION
    "v9.4.0"
    CACHE STRING "LVGL version")

set(LVED_USER_SRC_DIR
    ""
    CACHE STRING "User project source directory")

set(LVED_CODE_PROJECT_NAME
    "ui"
    CACHE STRING "Code Project name")

set(LVED_OUTPUT_DIR
    "preview-bin"
    CACHE STRING "Output directory")

set(LVED_ADDITIONAL_EXPORTED_FUNCTIONS
    ""
    CACHE STRING "Additional functions to export to JS")

# Env variables take precedence over cached values and values passed via `-D`
if(DEFINED ENV{LVED_STACK_SIZE})
  set(LVED_STACK_SIZE
      "$ENV{LVED_STACK_SIZE}"
      CACHE STRING "Emscripten stack size" FORCE)
else()
  set(LVED_STACK_SIZE
      "16777216"
      CACHE STRING "Emscripten stack size")
endif()

# Env variables take precedence over cached values and values passed via `-D`
if(DEFINED ENV{LVED_INITIAL_MEMORY})
  set(LVED_INITIAL_MEMORY
      "$ENV{LVED_INITIAL_MEMORY}"
      CACHE STRING "Emscripten initial memory" FORCE)
else()
  set(LVED_INITIAL_MEMORY
      "67108864"
      CACHE STRING "Emscripten initial memory")
endif()

if(NOT LVED_USER_SRC_DIR)
  message(STATUS "Building default runtime without a user project")
endif()

# ================= Settings =================

set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_COLOR_DIAGNOSTICS ON)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
set(CMAKE_EXECUTABLE_SUFFIX ".js")

# ================= Paths =================

set(LIBS_DIR "${CMAKE_CURRENT_LIST_DIR}/${LVED_LVGL_VERSION}/libs")
set(LIBS_LIB_DIR "${LIBS_DIR}/lib")
set(LIBS_INCLUDE_DIR "${LIBS_DIR}/include")

# ================= Environment =================

if(LVED_ENV STREQUAL "node")
  set(ENV_FLAG "-sENVIRONMENT=node")
else()
  set(ENV_FLAG "-sENVIRONMENT=web,worker")
endif()

# ================= Prebuilt libraries =================

set(LIBS m)

# LVGL
add_library(lvgl STATIC IMPORTED)
set_target_properties(
  lvgl PROPERTIES IMPORTED_LOCATION "${LIBS_LIB_DIR}/liblvgl.a"
                  INTERFACE_INCLUDE_DIRECTORIES "${LIBS_INCLUDE_DIR}")

list(APPEND LIBS lvgl)

# Runtime
add_library(lvrt STATIC IMPORTED)
set_target_properties(
  lvrt PROPERTIES IMPORTED_LOCATION "${LIBS_LIB_DIR}/liblvrt.a"
                  INTERFACE_INCLUDE_DIRECTORIES "${LIBS_INCLUDE_DIR}")

list(APPEND LIBS lvrt)

if(NOT LVED_LVGL_VERSION STREQUAL "v9.4.0")
  # The XML engine is part of LVGL for v9.4.0. After that release, we must also
  # add lv_xml as a library to link the project against it
  add_library(lv_xml STATIC IMPORTED)
  set_target_properties(
    lv_xml PROPERTIES IMPORTED_LOCATION "${LIBS_LIB_DIR}/liblv_xml.a"
                      INTERFACE_INCLUDE_DIRECTORIES "${LIBS_INCLUDE_DIR}")

  list(APPEND LIBS lv_xml)
endif()

# ================= User project =================

if(LVED_USER_SRC_DIR)
  add_compile_definitions(LV_EDITOR_PREVIEW=1 LV_USE_XML=1
                          "LV_CONF_PATH=\"${LIBS_INCLUDE_DIR}/lvgl/lv_conf.h\"")

  get_filename_component(LVED_USER_SRC_DIR "${LVED_USER_SRC_DIR}" ABSOLUTE)
  # Sibling projects declared via <folders> in project.xml (if any) are compiled and linked by
  # 'included_projects_gen.cmake', generated alongside this project's own CMakeLists.txt and
  # included from it - see project_generator.ts.
  add_subdirectory(${LVED_USER_SRC_DIR} ${CMAKE_CURRENT_BINARY_DIR}/project)
  target_link_libraries(lib-ui PRIVATE lvrt lvgl)

  list(APPEND LIBS lib-ui)
endif()

# ================= Executable =================

# Empty file purely to link everything together to produce the .js/.wasm output
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/empty.c" "")
add_executable(${LVED_PROJECT_NAME} "${CMAKE_CURRENT_BINARY_DIR}/empty.c")
target_link_libraries(${LVED_PROJECT_NAME} PRIVATE ${LIBS})

# ================= Emscripten =================

# Generate the flags
set(LVED_STACK_FLAG "-sSTACK_SIZE=${LVED_STACK_SIZE}")
set(LVED_INITIAL_MEMORY_FLAG "-sINITIAL_MEMORY=${LVED_INITIAL_MEMORY}")

if(EMSCRIPTEN)
  set(BASE_EXPORTED_FUNCTIONS
      _malloc
      _free
      _lvrt_initialize
      _lvrt_initialize_headless
      _lvrt_emscripten_get_heap_size
      _lvrt_emscripten_get_heap_max
      _lvrt_get_lvgl_version
      _lvrt_cleanup_runtime
      _lvrt_process_data
      _lvrt_xml_load_component_data
      _lvrt_component_create
      _lvrt_refresh
      _lvrt_resize_canvas
      _lvrt_get_obj_area
      _lvrt_apply_style
      _lvrt_xml_load_translations
      _lvrt_translation_set_language
      _lvrt_xml_test_register_from_data
      _lvrt_xml_test_run_init
      _lvrt_xml_test_run_next
      _lvrt_xml_test_run_stop
      _lvrt_set_subject
      _lvrt_set_subject_int
      _lvrt_set_subject_string
      _lvrt_set_subject_float
      _lvrt_subscribe_subject
      _lvrt_play_timeline
      _lvrt_get_view_type
      _lvrt_health_check)

  if(NOT LVED_LVGL_VERSION STREQUAL "v9.4.0")
    list(APPEND BASE_EXPORTED_FUNCTIONS _lvrt_set_target)
    list(APPEND BASE_EXPORTED_FUNCTIONS _get_screenshot)
  endif()

  if(LVED_USER_SRC_DIR)
    list(APPEND BASE_EXPORTED_FUNCTIONS _${LVED_CODE_PROJECT_NAME}_init)
  endif()

  if(LVED_USER_SRC_DIR)
    list(APPEND BASE_EXPORTED_FUNCTIONS _${LVED_CODE_PROJECT_NAME}_set_target)
  endif()

  if(LVED_ADDITIONAL_EXPORTED_FUNCTIONS)
    list(APPEND BASE_EXPORTED_FUNCTIONS ${LVED_ADDITIONAL_EXPORTED_FUNCTIONS})
  endif()

  list(REMOVE_DUPLICATES BASE_EXPORTED_FUNCTIONS)
  list(JOIN BASE_EXPORTED_FUNCTIONS "\",\"" EXPORTS_STR)

  set(EXPORTS_STR "[\"${EXPORTS_STR}\"]")

  target_compile_options(${LVED_PROJECT_NAME} PRIVATE "--use-port=sdl2")

  # Add -s ASSERTIONS=2 to get improved memory-error checks. Runs slower.
  target_link_options(
    ${LVED_PROJECT_NAME}
    PRIVATE
    "--no-entry"
    "-sWASM=1"
    "-sUSE_WEBGL2=0"
    "-sALLOW_MEMORY_GROWTH=1"
    "-sMAXIMUM_MEMORY=4gb"
    ${LVED_STACK_FLAG}
    ${LVED_INITIAL_MEMORY_FLAG}
    "-sNO_EXIT_RUNTIME=0"
    "-sEXPORTED_FUNCTIONS=${EXPORTS_STR}"
    "-sEXPORT_NAME=createModule"
    "-sFORCE_FILESYSTEM=1"
    "-sMODULARIZE=1"
    "-sEXPORTED_RUNTIME_METHODS=[ccall,cwrap,FS,addFunction,getValue,setValue,HEAPU8]"
    "-sUSE_SDL=2"
    "${OPT_FLAGS}"
    "${ENV_FLAG}")

  set_target_properties(${LVED_PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY
                                                        "${LVED_OUTPUT_DIR}")
endif()

# ================= Build log =================

if(DEFINED ADDITIONAL_EXPORTED_FUNCTIONS)
  file(APPEND "${LVED_OUTPUT_DIR}/build.log"
       "Additional: ${LVED_ADDITIONAL_EXPORTED_FUNCTIONS}\n")
endif()

file(APPEND "${LVED_OUTPUT_DIR}/build.log"
     "All exported functions: ${EXPORTED_FUNCTIONS_STR}\n")
if(NOT LVED_STACK_SIZE STREQUAL "")
  file(APPEND "${LVED_OUTPUT_DIR}/build.log"
       "Custom Emscripten stack size: ${LVED_STACK_SIZE}\n")
else()
  file(APPEND "${LVED_OUTPUT_DIR}/build.log"
       "Custom Emscripten stack size: default\n")
endif()
if(NOT LVED_INITIAL_MEMORY STREQUAL "")
  file(APPEND "${LVED_OUTPUT_DIR}/build.log"
       "Custom Emscripten initial memory: ${LVED_INITIAL_MEMORY}\n")
else()
  file(APPEND "${LVED_OUTPUT_DIR}/build.log"
       "Custom Emscripten initial memory: default\n")
endif()
