# Copyright 2022 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.

include_guard(GLOBAL)

cmake_minimum_required(VERSION 3.20)  # string(JSON)

include("$ENV{PW_ROOT}/pw_build/pigweed.cmake")

# Turns binary blobs into a C++ library of hard-coded byte arrays. The byte
# arrays are constant initialized and are safe to access at any time, including
# before main().
#
# Args:
#
#   HEADER
#
#     The header file to generate. Users will include this file exactly as it is
#     written here to reference the byte arrays.
#
#   NAMESPACE
#
#     The C++ namespace in which to place the generated blobs.
#
#   BLOB
#
#     A blob to be transformed from file to byte array. Multiple blobs may be
#     specified.
#
#     Blob args:
#
#       SYMBOL_NAME
#
#         The C++ symbol name for the byte array.
#
#       PATH
#
#         The file path for the binary blob.
#
#       LINKER_SECTION [optional]
#
#         If present, places the byte array in the specified linker section.
#
#       ALIGNAS [optional]
#
#         If present, the byte array is aligned as specified. The value of this
#         argument is used verbatim in an alignas() specifier for the blob
#         byte array.
#
function(pw_cc_blob_library NAME)
  cmake_parse_arguments(PARSE_ARGV 1 arg "" "HEADER;NAMESPACE" "")

  set(blobs ${arg_UNPARSED_ARGUMENTS})
  set(blob_files "")
  set(blob_index 0)
  set(json_blobs "[]")  # Create a JSON list of blobs

  pw_require_args("" arg_ HEADER NAMESPACE)

  while(NOT "${blobs}" STREQUAL "")
    list(POP_FRONT blobs first_arg)

    if(NOT "${first_arg}" STREQUAL BLOB)
      message(FATAL_ERROR "Invalid syntax in pw_cc_blob_library: "
        "Expected 'BLOB', found '${first_arg}'.")
    endif()

    list(FIND blobs BLOB blob_end)
    list(SUBLIST blobs 0 "${blob_end}" current_blob)

    cmake_parse_arguments(
      blob_arg "" "SYMBOL_NAME;PATH;LINKER_SECTION;ALIGNAS" "" "${current_blob}"
    )

    if(NOT "${blob_arg_UNPARSED_ARGUMENTS}" STREQUAL "")
      message(FATAL_ERROR "Unexpected BLOB arguments in pw_cc_blob_library: "
          "${blob_arg_UNPARSED_ARGUMENTS}")
    endif()

    pw_require_args("BLOB args for ${CMAKE_CURRENT_FUNCTION}"
         blob_arg_ PATH SYMBOL_NAME)

    cmake_path(ABSOLUTE_PATH blob_arg_PATH)
    list(APPEND blob_files "${blob_arg_PATH}")

    set(json_blob "{}")
    _pw_json_set_string_key(json_blob file_path "${blob_arg_PATH}")
    _pw_json_set_string_key(json_blob symbol_name "${blob_arg_SYMBOL_NAME}")

    if(NOT "${blob_arg_ALIGNAS}" STREQUAL "")
      _pw_json_set_string_key(json_blob alignas "${blob_arg_ALIGNAS}")
    endif()

    if(NOT "${blob_arg_LINKER_SECTION}" STREQUAL "")
      _pw_json_set_string_key(
          json_blob linker_section "${blob_arg_LINKER_SECTION}")
    endif()

    string(JSON json_blobs SET "${json_blobs}" "${blob_index}" "${json_blob}")

    if("${blob_end}" EQUAL -1)
      break()
    endif()

    list(SUBLIST blobs "${blob_end}" -1 blobs)
    math(EXPR blob_index "${blob_index}+1")
  endwhile()

  set(out_dir "${CMAKE_CURRENT_BINARY_DIR}/${NAME}")
  set(blob_json_file "${out_dir}/blobs.json")

  file(WRITE "${blob_json_file}" "${json_blobs}")
  set_property(  # Ensure the file is regenerated by CMake if it is deleted.
      DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${blob_json_file}")

  set(generated_header "${out_dir}/public/${arg_HEADER}")

  cmake_path(GET arg_HEADER STEM filename)
  set(generated_source "${out_dir}/${filename}.cc")

  add_custom_command(
    COMMAND
      python3
      "$ENV{PW_ROOT}/pw_build/py/pw_build/generate_cc_blob_library.py"
      --blob-file "${blob_json_file}"
      --header-include "${arg_HEADER}"
      --out-header "${generated_header}"
      --out-source "${generated_source}"
      --namespace "${arg_NAMESPACE}"
    DEPENDS
      "$ENV{PW_ROOT}/pw_build/py/pw_build/generate_cc_blob_library.py"
      "${blob_json_file}"
      ${blob_files}
    OUTPUT
      "${generated_header}"
      "${generated_source}"
  )

  add_custom_target("${NAME}._gen"
    DEPENDS
      "${generated_header}"
      "${generated_source}"
  )

  pw_add_library_generic("${NAME}" OBJECT
    SOURCES
      "${generated_source}"
    PUBLIC_INCLUDES
      "${out_dir}/public"
    PUBLIC_DEPS
      pw_polyfill
      pw_preprocessor
  )
  add_dependencies("${NAME}" "${NAME}._gen")
endfunction(pw_cc_blob_library)

# Sets a key with a string value in a JSON object.
macro(_pw_json_set_string_key json_var key value)
  string(JSON "${json_var}" SET "${${json_var}}" "${key}" "\"${value}\"")
endmacro()
