cmake_minimum_required(VERSION 3.14)

set(RC_COMPILE_SCRIPT "${CMAKE_CURRENT_LIST_FILE}")


function(add_resource_object name)
  cmake_parse_arguments(ARG "STATIC;SHARED;OBJECT;"
                        "NAMESPACE;HEADER_FILE;ASM_FILE" ";" ${ARGN})

  set(rc_asm_file "${CMAKE_CURRENT_BINARY_DIR}/${name}.rc.S")
  set(rc_header_file "${CMAKE_CURRENT_BINARY_DIR}/${name}.hxx")
  # set(rc_source_file "${CMAKE_CURRENT_BINARY_DIR}/${name}.cxx")

  if(ARG_NAMESPACE)
    set(rc_namespace "${ARG_NAMESPACE}")
  else()
    string(MAKE_C_IDENTIFIER "${name}" rc_namespace)
  endif()

  if(ARG_HEADER_FILE)
    set(rc_header_file "${ARG_HEADER_FILE}")
  endif()

  if(ARG_ASM_FILE)
    set(rc_asm_file "${ARG_ASM_FILE}")
  endif()

  set(rc_source_list "")
  set(rc_depends "")
  foreach(source_file ${ARG_UNPARSED_ARGUMENTS})
    # file(REAL_PATH "${source_file}" source_path)
    list(APPEND rc_source_list "${source_file}")
    string(FIND ${source_file} = equal_sign_index)
    if (equal_sign_index STREQUAL -1)
        list(APPEND rc_depends ${source_file})
    else ()
        string(SUBSTRING ${source_file} 0 ${equal_sign_index} file_path)
        list(APPEND rc_depends ${file_path})
    endif ()
  endforeach(source_file)

  add_custom_command(
    OUTPUT "${rc_asm_file}" "${rc_header_file}"
    COMMAND
      "${CMAKE_COMMAND}" 
    ARGS "-DRC_ASM_FILE=${rc_asm_file}"
      "-DTEST_ARG=a;b;c"
      "-DRC_HEADER_FILE=${rc_header_file}"
      "-DRC_SOURCE_LIST=${rc_source_list}"
      "-DRC_NAMESPACE=${rc_namespace}"
      "-DRC_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}"
      "-P" "${RC_COMPILE_SCRIPT}"
    DEPENDS ${rc_depends} ${RC_COMPILE_SCRIPT}
    VERBATIM)

  add_library(${name} OBJECT "${rc_asm_file}" "${rc_header_file}")

  target_include_directories(${name} INTERFACE ${CMAKE_CURRENT_BINARY_DIR})
  target_compile_features(${name} INTERFACE cxx_std_17)
endfunction()

if(RC_ASM_FILE)
  set(class_definition
      "#pragma once
#include <cstddef>
#include <iostream>
#include <string>
#include <optional>

namespace rc {

#ifndef RC_CLASS_DEFINED
#define RC_CLASS_DEFINED
    class embedded_object {
        const char *start_, *end_;

    public:
        constexpr embedded_object(const char *start, const char *end)
            : start_(start), end_(end)
        {}

        constexpr embedded_object(const char *start, std::size_t size)
            : start_(start), end_(start + size)
        {}

        const char *begin() const { return start_; }
        const char *end() const { return end_; }
        const std::size_t size() const { return end_ - start_; }

        std::string_view to_string_view() const { return std::string_view(static_cast<const char *>(begin()), size()); }
        std::string to_string() const { return std::string(to_string_view()); }
    };

    class embedded_file_buf : public std::streambuf {
    public:
        explicit embedded_file_buf(embedded_object obj)
        {
            char *start_pos = const_cast<char *>(obj.begin());
            char *end_pos = const_cast<char *>(obj.end());
            setg(start_pos, start_pos, end_pos);
        }
    };

    class embedded_ifstream : public std::istream
    {
        std::optional<embedded_file_buf> file_buf_;

    public:
        explicit embedded_ifstream(embedded_object obj)
        {
            open(obj);
        }

        embedded_ifstream() = default;

        void open(embedded_object obj)
        {
            file_buf_.emplace(obj);
            rdbuf(&file_buf_.value());
        }

        void close()
        {
            rdbuf(nullptr);
            file_buf_.reset();
        }
    };

    #endif // RC_CLASS_DEFINED

}
")

  set(addresses "")

  set(objects "")

  set(asm_content "")

  set(list ${RC_SOURCE_LIST})
  foreach(source ${list})
    string(FIND ${source} = equal_sign_index)
    if (equal_sign_index STREQUAL -1)
        file(RELATIVE_PATH file_path "${RC_SOURCE_DIR}" "${source}")
        string(MAKE_C_IDENTIFIER "${file_path}" identifier)
        set (file_path ${file_path})
    else ()
        string(SUBSTRING ${source} 0 ${equal_sign_index} file_path)
        math(EXPR identifier_start "${equal_sign_index} + 1" OUTPUT_FORMAT DECIMAL) 
        string(SUBSTRING ${source} ${identifier_start} -1 identifier_raw)
        string(MAKE_C_IDENTIFIER ${identifier_raw} identifier)
    endif ()

    set(asm_symbol "${RC_NAMESPACE}.${identifier}")

    string(
      APPEND
      addresses
      "extern const unsigned int ${identifier}_size asm(\"${asm_symbol}.size\");
extern const char ${identifier}[] asm(\"${asm_symbol}\");
")

    string(
      APPEND
      objects
      "inline const embedded_object ${identifier}{ addresses::${identifier}, addresses::${identifier}_size };
")

    string(
      APPEND
      asm_contents
      "    .section .rodata
    .global ${asm_symbol}
    .type ${asm_symbol}, object
    .global ${asm_symbol}.size
    .align 8
    .type ${asm_symbol}.size, object
${asm_symbol}.size:
    .int  ${asm_symbol}.end - ${asm_symbol}, 0
${asm_symbol}:
    .incbin \"${file_path}\"
${asm_symbol}.end:
    .section rc.size
")

  endforeach(source)

  set(resource_declaration
      "namespace ${RC_NAMESPACE} { namespace addresses {
${addresses}
} }

namespace ${RC_NAMESPACE} {
${objects}
}
")

  file(WRITE "${RC_HEADER_FILE}" "${class_definition}${resource_declaration}")

  file(WRITE "${RC_ASM_FILE}" "${asm_contents}")

endif()
