# Most of the functions in here are macros since the findModule defines its own scipe any way!
function(cmakejson_validate_find_module_json _input)
    list(APPEND CMAKE_MESSAGE_CONTEXT "validate")
    #TODO: Validate all fields in the input
    list(POP_BACK CMAKE_MESSAGE_CONTEXT)
endfunction()

function(cmakejson_gather_json_array_configuration_as_list _prefix _outvar)
    set(element ${${_prefix}_CONFIG})
    cmakejson_run_func_over_parsed_range(${_prefix}_LIB_PREFIXES cmakejson_gather_json_array_as_list _prefixes)
    cmakejson_run_func_over_parsed_range(${_prefix}_LIB_SUFFIXES cmakejson_gather_json_array_as_list _suffixes)
    set(CMakeJSON_MODULE_${element}_LIB_PREFIXES "${_prefixes}" PARENT_SCOPE)
    set(CMakeJSON_MODULE_${element}_LIB_SUFFIXES "${_suffixes}" PARENT_SCOPE)
    list(APPEND ${_outvar} ${element})
    set(${_outvar} ${${_outvar}} PARENT_SCOPE)
endfunction()

function(cmakejson_create_imported_targets _prefix _found_vars)
    set(_target ${CMAKE_FIND_PACKAGE_NAME}::${${_prefix}_NAME})
    if(NOT TARGET ${_target})
        cmakejson_message_if(CMakeJSON_DEBUG_FIND_MODULE "Searching for target: '${${_prefix}_NAME}'!")
        set(_mod_found_name ${CMAKE_FIND_PACKAGE_NAME}_TARGET_${${_prefix}_NAME}_FOUND)
        set(${_mod_found_name} FALSE PARENT_SCOPE)
        list(APPEND ${_found_vars} ${_mod_found_name})
        set(${_found_vars} ${${_found_vars}} PARENT_SCOPE)

        set(_type UNKNOWN)
        if(NOT DEFINED ${_prefix}_LIBRARY_NAMES)
            set(_type INTERFACE)
        endif()
        add_library(${_target} ${_type} IMPORTED)
        cmakejson_run_func_over_parsed_range(${_prefix}_LIBRARY_NAMES cmakejson_gather_json_array_as_list _lib_names)
        foreach(_config IN LISTS CMakeJSON_MODULE_CONFIGURATIONS)
            set(additional_lib_names)
            foreach(_lib_prefix IN LISTS CMakeJSON_MODULE_${_config}_LIB_PREFIXES)
                set(_tmp_lib_names "${_lib_names}")
                list(TRANSFORM _tmp_lib_names PREPEND "${_lib_prefix}")
                list(APPEND additional_lib_names ${_tmp_lib_names})
                foreach(_lib_suffix IN LISTS CMakeJSON_MODULE_${_config}_LIB_SUFFIXES)
                    list(TRANSFORM _tmp_lib_names APPEND "${_lib_suffix}")
                    list(APPEND additional_lib_names ${_lib_name}) 
                endforeach()
            endforeach()
            foreach(_lib_suffix IN LISTS CMakeJSON_MODULE_${_config}_LIB_SUFFIXES)
                set(_tmp_lib_names "${_lib_names}")
                list(TRANSFORM _tmp_lib_names APPEND "${_lib_suffix}")
                list(APPEND additional_lib_names ${_tmp_lib_names})
                foreach(_lib_prefix IN LISTS CMakeJSON_MODULE_${_config}_LIB_PREFIXES)
                    list(TRANSFORM _tmp_lib_names PREPEND "${_lib_prefix}")
                    list(APPEND additional_lib_names ${_lib_name}) 
                endforeach()
            endforeach()
            if(_config STREQUAL "DEBUG")
                find_library(${CMAKE_FIND_PACKAGE_NAME}_${${_prefix}_NAME}_LIBRARY_${_config} NAMES ${_lib_names} ${additional_lib_names} NAMES_PER_DIR ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/debug/lib NO_DEFAULT_PATH)
            elseif(_config STREQUAL "RELEASE")
                find_library(${CMAKE_FIND_PACKAGE_NAME}_${${_prefix}_NAME}_LIBRARY_${_config} NAMES ${_lib_names} ${additional_lib_names} NAMES_PER_DIR ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/lib NO_DEFAULT_PATH)
            endif()
            set(_all_lib_names ${_lib_names})
            list(APPEND _all_lib_names ${additional_lib_names})
            cmakejson_message_if(CMakeJSON_DEBUG_FIND_MODULE "Library search names: '${_all_lib_names}'")
            if(NOT ${CMAKE_FIND_PACKAGE_NAME}_${${_prefix}_NAME}_LIBRARY_${_config})
                unset(${CMAKE_FIND_PACKAGE_NAME}_${${_prefix}_NAME}_LIBRARY_${_config})
                find_library(${CMAKE_FIND_PACKAGE_NAME}_${${_prefix}_NAME}_LIBRARY_${_config} NAMES ${_all_lib_names} PATH_SUFFIXES lib NAMES_PER_DIR)
            endif()
            cmakejson_print_variables_if(CMakeJSON_DEBUG_FIND_MODULE "${CMAKE_FIND_PACKAGE_NAME}_${${_prefix}_NAME}_LIBRARY_${_config}")
            if(${CMAKE_FIND_PACKAGE_NAME}_${${_prefix}_NAME}_LIBRARY_${_config})
                set_target_properties(${_target} PROPERTIES
                                                    IMPORTED_LOCATION_${_config}
                                                    ${${CMAKE_FIND_PACKAGE_NAME}_${${_prefix}_NAME}_LIBRARY_${_config}}
                                                    IMPORTED_LINK_INTERFACE_LANGUAGES
                                                    ${CMakeJSON_MODULE_LANGUAGES}
                                                    )
                set(${_mod_found_name} TRUE PARENT_SCOPE)
            else()
                cmakejson_message_if(CMakeJSON_DEBUG_FIND_MODULE "Couldn't find library in config '${_config}' for target: ${${_prefix}_NAME}!")
            endif()
        endforeach()
        cmakejson_run_func_over_parsed_range(${_prefix}_HEADER_FILE cmakejson_gather_json_array_as_list _header)
        find_file(_cmakejson_header_path NAMES ${_header} PATH_SUFFIXES "include")
        if(_cmakejson_module_header_path)
            get_filename_component(${CMAKE_FIND_PACKAGE_NAME}_${${_prefix}_NAME}_INCLUDE_DIR "${_cmakejson_module_header_path}" DIRECTORY CACHE)
        endif()
        unset(_cmakejson_module_header_path CACHE)
        if(${CMAKE_FIND_PACKAGE_NAME}_${${_prefix}_NAME}_INCLUDE_DIR)
        target_include_directories(${_target} INTERFACE ${${CMAKE_FIND_PACKAGE_NAME}_${${_prefix}_NAME}_INCLUDE_DIR})
        endif()
        cmakejson_run_func_over_parsed_range(${_prefix}_LINK_LIBRARIES cmakejson_gather_json_array_as_list _link_libs)
        if(_link_libs)
            target_link_libraries(${_target} INTERFACE ${_link_libs})
        endif()
        cmakejson_run_func_over_parsed_range(${_prefix}_INCLUDE_DIRECTORIES cmakejson_gather_json_array_as_list _includes)
        if(_includes)
            target_include_directories(${_target} INTERFACE ${_includes})
        endif()
    endif()
endfunction(cmakejson_create_imported_targets)

function(cmakejson_check_config_targets_vs_imported_targets)
    #TODO
endfunction()

macro(cmakejson_find_package_config)
    list(APPEND CMAKE_MESSAGE_CONTEXT "conifg_search")
    if(NOT DEFINED CMakeJSON_PARSE_MODULE_CONFIG_NAME)
        set(CMakeJSON_PARSE_MODULE_CONFIG_NAME "${CMAKE_FIND_PACKAGE_NAME}")
    endif()
    if(DEFINED ${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS)
        list(APPEND config_find_str COMPONENTS "${${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS}")
    endif()
    find_package(${CMakeJSON_PARSE_MODULE_CONFIG_NAME})
    if(${CMakeJSON_PARSE_MODULE_CONFIG_NAME}_FOUND)
        #TODO check expected imported targets!
        #TODO check config_name
        find_package_handle_standard_args(${CMAKE_FIND_PACKAGE_NAME} 
                                          HANDLE_VERSION_RANGE 
                                          HANDLE_COMPONENTS
                                          CONFIG_MODE
                                         )
    endif()
    list(POP_BACK CMAKE_MESSAGE_CONTEXT)
endmacro()

function(cmakejson_redirect_imported_targets_to_pkgconfig_target _prefix _pkgconfigtarget)
    set(_target ${CMAKE_FIND_PACKAGE_NAME}::${${_prefix}_NAME})
    if(NOT TARGET ${_target})
        add_library(${_target} INTERFACE IMPORTED)
        target_link_libraries(${_target} INTERFACE ${_pkgconfigtarget})
    endif()
endfunction()

macro(cmakejson_find_package_pkgconfig)
    list(APPEND CMAKE_MESSAGE_CONTEXT "pkgconfig_search")
    if(NOT DEFINED CMakeJSON_PARSE_MODULE_PKG_CONFIG_NAME)
        string(TOLOWER "${CMAKE_FIND_PACKAGE_NAME}" CMakeJSON_PARSE_MODULE_PKG_CONFIG_NAME)
    endif()
    #TODO add pkg specs
    set(_versionstr)
    if(PACKAGE_FIND_VERSION_MIN)
        set(_versionstr ">=${PACKAGE_FIND_VERSION_MIN}")
    endif()
    pkg_check_modules(${CMAKE_FIND_PACKAGE_NAME}_PC IMPORTED_TARGET "${CMakeJSON_PARSE_MODULE_PKG_CONFIG_NAME}${_versionstr}")
    unset(_versionstr)
    list(POP_BACK CMAKE_MESSAGE_CONTEXT)
endmacro()

function(cmakejson_setup_module_dependencies _prefix)
    if(DEFINED ${_prefix})
        set(dep_name ${${_prefix}})
    elseif(DEFINED ${_prefix}_NAME)
        set(dep_name ${${_prefix}_NAME})
    else()
        message(${CMakeJSON_MSG_ERROR_TYPE} "Dependency parsed as '${_prefix}' is missing a name!")
    endif()
    set(_findstr "${dep_name}")
    if(${_prefix}_VERSION)
        list(APPEND _findstr "${${_prefix}_VERSION}")
    endif()
    if(${_prefix}_COMPONENTS)
        list(APPEND _findstr COMPONENTS "${${_prefix}_COMPONENTS}")
    endif()
    if(NOT DEFINED ${_prefix}_CONDITION)
        set(${_prefix}_CONDITION TRUE)
    endif()
    if(${${_prefix}_CONDITION})
        find_package(${_findstr} ${${_prefix}_FIND_OPTIONS})
    endif()
endfunction(cmakejson_setup_module_dependencies _prefix)


macro(cmakejson_find_module _contents)
    cmakejson_validate_find_module_json("${_contents}")

    list(APPEND CMAKE_MESSAGE_CONTEXT "parse")
    cmakejson_parse_json(JSON_INPUT "${_contents}"
                         VARIABLE_PREFIX "MODULE"
                         OUTPUT_LIST_CREATED_VARIABLES "TARGET_PARSED_VARIABLES"
    )
    list(POP_BACK CMAKE_MESSAGE_CONTEXT)

    if(CMakeJSON_PARSE_MODULE_DESCRIPTION)
        set_package_properties(${CMAKE_FIND_PACKAGE_NAME} PROPERTIES
                               DESCRIPTION ${CMakeJSON_PARSE_MODULE_DESCRIPTION}
                              )
    endif()
    if(CMakeJSON_PARSE_MODULE_HOMEPAGE)
        set_package_properties(${CMAKE_FIND_PACKAGE_NAME} PROPERTIES
                               URL ${CMakeJSON_PARSE_MODULE_HOMEPAGE}
                               )
    endif()
    # Search via config
    if(NOT CMakeJSON_PARSE_MODULE_SKIP_CONFIG_MODE)
        cmakejson_find_package_config()
    endif()
    # Search via pkgconfig or find_library
    if(NOT ${CMAKE_FIND_PACKAGE_NAME}_FOUND)
        find_package(PkgConfig)
        if(PkgConfig_FOUND)
            cmakejson_find_package_pkgconfig()
        endif()
        if(NOT ${CMAKE_FIND_PACKAGE_NAME}_PKGCONFIG)
            foreach(_component IN LISTS ${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS)
                find_package(${CMAKE_FIND_PACKAGE_NAME}_${_component})
            endforeach()
            cmakejson_run_func_over_parsed_range(CMakeJSON_PARSE_MODULE_DEPENDENCIES cmakejson_setup_module_dependencies)
            cmakejson_run_func_over_parsed_range(CMakeJSON_PARSE_MODULE_CONFIGURATIONS cmakejson_gather_json_array_configuration_as_list CMakeJSON_MODULE_CONFIGURATIONS)
            if(NOT CMakeJSON_MODULE_CONFIGURATIONS)
                set(CMakeJSON_MODULE_CONFIGURATIONS "DEBUG" "RELEASE")
                set(CMakeJSON_MODULE_DEBUG_LIB_SUFFIXES "d" "_d")
            endif()
            cmakejson_run_func_over_parsed_range(${_prefix}_LIBRARY_NAMES cmakejson_gather_json_array_as_list CMakeJSON_MODULE_LANGUAGES)
            if(NOT CMakeJSON_MODULE_LANGUAGES)
                set(CMakeJSON_MODULE_LANGUAGES "C") # TODO: maybe switch that one the header file ending?
            endif()
            cmakejson_run_func_over_parsed_range(CMakeJSON_PARSE_MODULE_IMPORTED_TARGETS cmakejson_create_imported_targets _found_targets)
            if(DEFINED CMakeJSON_PARSE_MODULE_VERSION_EXTRACTION_FILE)
                file(READ "${CMakeJSON_PARSE_MODULE_VERSION_EXTRACTION_FILE}" _contents)
                string(REGEX REPLACE "${CMakeJSON_PARSE_MODULE_VERSION_EXTRACTION_REGEX}" "\\1" ${CMAKE_FIND_PACKAGE_NAME}_VERSION ${_contents})
            endif()
            set(_version_check)
            if(${CMAKE_FIND_PACKAGE_NAME}_VERSION)
                set(_version_check VERSION_VAR ${CMAKE_FIND_PACKAGE_NAME}_VERSION HANDLE_VERSION_RANGE)
            endif()
            find_package_handle_standard_args(${CMAKE_FIND_PACKAGE_NAME} 
                REQUIRED_VARS ${_found_targets}
                HANDLE_COMPONENTS
                ${_version_check}
            )
            unset(_version_check)
            unset(_found_targets)
        else()
            # Pkgconfig available -> All imported targets get redirected to the pkgconfig target
            cmakejson_run_func_over_parsed_range(CMakeJSON_PARSE_MODULE_IMPORTED_TARGETS cmakejson_redirect_imported_targets_to_pkgconfig_target "PkgConfig::${CMAKE_FIND_PACKAGE_NAME}_PC")
            find_package_handle_standard_args(  ${CMAKE_FIND_PACKAGE_NAME} 
                                                VERSION_VAR ${CMAKE_FIND_PACKAGE_NAME}_PC_VERSION
                                                HANDLE_VERSION_RANGE
                                             )
        endif()
    endif()
    # Use Fetch_Content / ExternalProject_Add ? CPM or whatever?
    if(NOT ${CMAKE_FIND_PACKAGE_NAME}_FOUND)
        # TODO: Run fetchcontent or external_project_add?
    endif()

endmacro()

macro(cmakejson_find_module_file _file)
    get_filename_component(_current_file_name "${CMAKE_CURRENT_LIST_FILE}" NAME_WE)
    string(REGEX REPLACE "[Ff][Ii][Nn][Dd]" "" _package_name "${_current_file_name}")
    if(NOT _package_name STREQUAL CMAKE_FIND_PACKAGE_NAME)
        message(${CMakeJSON_MSG_ERROR_TYPE} "CMAKE_FIND_PACKAGE_NAME='${CMAKE_FIND_PACKAGE_NAME}' does not agree in case with Find<Name>.cmake (Name='${_package_name}')\n To be crossplatform compatible the names need to agree in case!")
    endif()
    file(TO_CMAKE_PATH "${_file}" _file)
    get_filename_component(file "${_file}" ABSOLUTE)
    if(NOT EXISTS "${file}")
        message(FATAL_ERROR "File '${_file}' does not exists!")
    endif()
    message(${CMakeJSON_MSG_VERBOSE_TYPE} "Creating module from file: '${_file}'")
    get_filename_component(_filename "${_file}" NAME_WE)
    file(READ "${file}" _contents)
    cmakejson_find_module("${_contents}")
    set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${file}")
    unset(_contents)
    unset(_filename)
    unset(_file)
    unset(file)
endmacro()