# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you 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
# 
#   http://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.

##### setup docker target
if (APPLE) #create filesystem script is not working on mac os, exclude target from ALL
    add_custom_target(celix-build-docker-dirs 
        DEPENDS $<TARGET_PROPERTY:celix-build-docker-dirs,DOCKER_DEPS>
    )
else ()
    add_custom_target(celix-build-docker-dirs ALL
        DEPENDS $<TARGET_PROPERTY:celix-build-docker-dirs,DOCKER_DEPS>
    )
endif ()
set_target_properties(celix-build-docker-dirs PROPERTIES "DOCKER_DEPS" "") #initial empty deps list

add_custom_target(celix-build-docker-images)
set(DOCKER_USE_SUDO ON CACHE BOOL "Wether the use of sudo is needed to run docker")
set(DOCKER_CMD "docker" CACHE STRING "Docker command to use.")

get_directory_property(CLEANFILES ADDITIONAL_MAKE_CLEAN_FILES)
list(APPEND CLEANFILES "${CMAKE_BINARY_DIR}/docker")
set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${CLEANFILES}")
#####

function(add_celix_docker)
    list(GET ARGN 0 DOCKER_TARGET)
    list(REMOVE_AT ARGN 0)

    set(OPTIONS CXX)
    set(ONE_VAL_ARGS GROUP NAME FROM BUNDLES_DIR WORKDIR IMAGE_NAME)
    set(MULTI_VAL_ARGS BUNDLES PROPERTIES INSTRUCTIONS)
    cmake_parse_arguments(DOCKER "${OPTIONS}" "${ONE_VAL_ARGS}" "${MULTI_VAL_ARGS}" ${ARGN})

    #set defaults
    if (NOT DOCKER_FROM)
        set(DOCKER_FROM "scratch")
        set(DOCKER_CREATE_FS true)
    else()
        set(DOCKER_CREATE_FS false)
    endif()
    if (NOT DOCKER_NAME)
        set(DOCKER_NAME "${DOCKER_TARGET}")
    endif()
    if (NOT DOCKER_IMAGE_NAME)
        set(DOCKER_IMAGE_NAME "${DOCKER_NAME}")
    endif()
    if (NOT DOCKER_WORKDIR)
        set(DOCKER_WORKDIR "/root")
    endif()
    if (NOT DOCKER_GROUP)
        set(DOCKER_LOC "${CMAKE_BINARY_DIR}/docker/${DOCKER_NAME}")
    else()
        set(DOCKER_LOC "${CMAKE_BINARY_DIR}/docker/${DOCKER_GROUP}/${DOCKER_NAME}")
    endif()
    if (NOT DOCKER_BUNDLES_DIR)
        set(DOCKER_BUNDLES_DIR "/bundles")
    endif()

    #ensure the docker dir will be deleted during clean
    get_directory_property(CLEANFILES ADDITIONAL_MAKE_CLEAN_FILES)
    list(APPEND CLEANFILES "$<TARGET_PROPERTY:${DOCKER_TARGET},DOCKER_LOC>")
    set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${CLEANFILES}")

    if (DOCKER_LAUNCHER_SRC)
        get_filename_component(SRC_FILENAME ${DOCKER_LAUNCHER_SRC} NAME)
        set(LAUNCHER_SRC "${PROJECT_BINARY_DIR}/celix/gen/${DOCKER_TARGET}-${SRC_FILENAME}")
        set(LAUNCHER_ORG "${DOCKER_LAUNCHER_SRC}")
    elseif (DOCKER_CXX)
        set(LAUNCHER_SRC "${PROJECT_BINARY_DIR}/celix/gen/${DOCKER_TARGET}-main.cc")
        set(LAUNCHER_ORG "${CELIX_CMAKE_DIRECTORY}/cmake_celix/main.c.in")
    else()
        set(LAUNCHER_SRC "${PROJECT_BINARY_DIR}/celix/gen/${DOCKER_TARGET}-main.c")
        set(LAUNCHER_ORG "${CELIX_CMAKE_DIRECTORY}/cmake_celix/main.c.in")
    endif()

    if (DOCKER_LAUNCHER)
        add_custom_target(${DOCKER_TARGET})
        if (IS_ABSOLUTE "${DOCKER_LAUNCHER}")
            set(LAUNCHER "${DOCKER_LAUNCHER}")
            get_filename_component(EXE_FILENAME ${DOCKER_LAUNCHER} NAME)
            set(DOCKER_ENTRYPOINT "ENTRYPOINT [\"/bin/${EXE_FILENAME}\"]")
        else()
            #assuming target
            set(LAUNCHER "$<TARGET_FILE:${DOCKER_LAUNCHER}>")
            set(DOCKER_ENTRYPOINT "ENTRYPOINT [\"/bin/$<TARGET_FILE_NAME:${DOCKER_TARGET}>\"]")
        endif()
    else ()
        add_custom_command(OUTPUT ${LAUNCHER_SRC}
                COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/celix/gen
        )

        if (DOCKER_CXX)
            set(LAUNCHER_STAGE1 "${CMAKE_CURRENT_BINARY_DIR}/${DOCKER_TARGET}-docker-main-stage1.cc")
        else()
            set(LAUNCHER_STAGE1 "${CMAKE_CURRENT_BINARY_DIR}/${DOCKER_TARGET}-docker-main-stage1.c")
        endif()

        file(GENERATE
                OUTPUT "${LAUNCHER_STAGE1}"
                CONTENT "#include <celix_launcher.h>

int main(int argc, char *argv[]) {
    const char * config = \"cosgi.auto.start.1=$<JOIN:$<TARGET_PROPERTY:${DOCKER_TARGET},DOCKER_BUNDLES>, >\\n\\
$<JOIN:$<TARGET_PROPERTY:${DOCKER_TARGET},DOCKER_PROPERTIES>,\\n\\
>\";

    properties_pt packedConfig = properties_loadFromString(config);

    return celixLauncher_launchWithArgsAndProps(argc, argv, packedConfig);
}
"
        )

        file(GENERATE
                OUTPUT "${LAUNCHER_SRC}"
                INPUT "${LAUNCHER_STAGE1}"
        )

        add_executable(${DOCKER_TARGET} EXCLUDE_FROM_ALL ${LAUNCHER_SRC})
        #   set_target_properties(${DOCKER_TARGET} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${DOCKER_LOC})
        target_include_directories(${DOCKER_TARGET} PRIVATE ${CELIX_INCLUDE_DIRS})
        target_link_libraries(${DOCKER_TARGET} PRIVATE ${CELIX_FRAMEWORK_LIBRARY} ${CELIX_UTILS_LIBRARY})
        set(LAUNCHER "$<TARGET_FILE:${DOCKER_TARGET}>")
        set(DOCKER_ENTRYPOINT "ENTRYPOINT [\"/bin/$<TARGET_FILE_NAME:${DOCKER_TARGET}>\"]")
    endif ()


    ###### Setup docker custom target timestamp
    add_custom_target(${DOCKER_TARGET}-deps
        DEPENDS ${FS_TIMESTAMP_FILE} $<TARGET_PROPERTY:${DOCKER_TARGET},DOCKER_DEPS>
    )
    add_dependencies(${DOCKER_TARGET} ${DOCKER_TARGET}-deps)

    #setup dependencies based on timestamp
    if (DOCKER_CREATE_FS)
        add_custom_command(TARGET ${DOCKER_TARGET} POST_BUILD
	        COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_PROPERTY:${DOCKER_TARGET},DOCKER_LOC>
	        COMMAND cd $<TARGET_PROPERTY:${DOCKER_TARGET},DOCKER_LOC> && /bin/bash ${CELIX_CMAKE_DIRECTORY}/cmake_celix/create_target_filesystem.sh -e ${LAUNCHER} > /dev/null
    	    WORKING_DIRECTORY "${DOCKER_LOC}"
            COMMENT "Creating docker dir for ${DOCKER_TARGET}" VERBATIM
        )
    else ()
        add_custom_command(TARGET ${DOCKER_TARGET} POST_BUILD
	        COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_PROPERTY:${DOCKER_TARGET},DOCKER_LOC>/bin
            COMMAND ${CMAKE_COMMAND} -E copy_if_different ${LAUNCHER} $<TARGET_PROPERTY:${DOCKER_TARGET},DOCKER_LOC>/bin/
	        WORKING_DIRECTORY "${DOCKER_LOC}"
            COMMENT "Creating docker dir for ${DOCKER_TARGET}" VERBATIM
        )
    endif ()

    ##### Deploy Target Properties for Dockerfile #####
    set_target_properties(${DOCKER_TARGET} PROPERTIES "DOCKER_LOC" "${DOCKER_LOC}")
    set_target_properties(${DOCKER_TARGET} PROPERTIES "DOCKER_FROM" "${DOCKER_FROM}") #name of docker base, default celix-base
    set_target_properties(${DOCKER_TARGET} PROPERTIES "DOCKER_IMAGE_NAME" "${DOCKER_IMAGE_NAME}") #name of docker images, default deploy target name
    set_target_properties(${DOCKER_TARGET} PROPERTIES "DOCKER_BUNDLES_DIR" "${DOCKER_BUNDLES_DIR}") #bundles directory in docker image
    set_target_properties(${DOCKER_TARGET} PROPERTIES "DOCKER_BUNDLES" "") #bundles
    set_target_properties(${DOCKER_TARGET} PROPERTIES "DOCKER_WORKDIR" "${DOCKER_WORKDIR}") #workdir in docker image, should also contain the config.properties
    set_target_properties(${DOCKER_TARGET} PROPERTIES "DOCKER_ENTRYPOINT" "${DOCKER_ENTRYPOINT}")
    set_target_properties(${DOCKER_TARGET} PROPERTIES "DOCKER_CREATE_FS" "${DOCKER_CREATE_FS}") #wether to create a fs with the minimal needed libraries / etc files
    set_target_properties(${DOCKER_TARGET} PROPERTIES "DOCKER_INSTRUCTIONS" "") #list of additional instructions
    set_target_properties(${DOCKER_TARGET} PROPERTIES "DOCKER_PROPERTIES" "")
    set_target_properties(${DOCKER_TARGET} PROPERTIES "DOCKER_DEPS" "")

    set(DOCKERFILE_STAGE1 ${CMAKE_BINARY_DIR}/celix/gen/${DOCKER_TARGET}-Dockerfile.in)
    set(DOCKERFILE "$<TARGET_PROPERTY:${DOCKER_TARGET},DOCKER_LOC>/Dockerfile")

    file(GENERATE
            OUTPUT "${DOCKERFILE_STAGE1}"
            CONTENT "# Dockerfile for celix based image
FROM $<TARGET_PROPERTY:${DOCKER_TARGET},DOCKER_FROM>
ENV IMAGE_NAME $<TARGET_PROPERTY:${DOCKER_TARGET},DOCKER_IMAGE_NAME>
COPY . /
WORKDIR $<TARGET_PROPERTY:${DOCKER_TARGET},DOCKER_WORKDIR>
$<TARGET_PROPERTY:${DOCKER_TARGET},DOCKER_ENTRYPOINT>
$<JOIN:$<TARGET_PROPERTY:${DOCKER_TARGET},DOCKER_INSTRUCTIONS>,
>
")
    file(GENERATE
            OUTPUT ${DOCKERFILE}
            INPUT ${DOCKERFILE_STAGE1}
    )

    #generate config.properties
    set(DOCKER_PROPERTIES_FILE "${DOCKER_LOC}/${DOCKER_WORKDIR}/config.properties")
    set(STAGE1_PROPERTIES_FILE "${CMAKE_BINARY_DIR}/celix/gen/${DOCKER_TARGET}-docker-config-stage1.properties")
    file(GENERATE
            OUTPUT "${STAGE1_PROPERTIES_FILE}"
            CONTENT "cosgi.auto.start.1=$<JOIN:$<TARGET_PROPERTY:${DOCKER_TARGET},DOCKER_BUNDLES>, >
$<JOIN:$<TARGET_PROPERTY:${DOCKER_TARGET},DOCKER_PROPERTIES>,
>
"
    )
    file(GENERATE
            OUTPUT "${DOCKER_PROPERTIES_FILE}"
            INPUT "${STAGE1_PROPERTIES_FILE}"
    )

    if (DOCKER_BUNDLES)
        celix_docker_bundles(${DOCKER_TARGET} ${DOCKER_BUNDLES})
    endif()
    if (DOCKER_PROPERTIES)
        celix_docker_properties(${DOCKER_TARGET} ${DOCKER_PROPERTIES})
    endif ()
    if (DOCKER_INSTRUCTIONS)
        celix_docker_instructions(${DOCKER_TARGET} ${DOCKER_INSTRUCTIONS})
    endif ()

    get_target_property(DEPS celix-build-docker-dirs "DOCKER_DEPS")
    list(APPEND DEPS ${DOCKER_TARGET})
    set_target_properties(celix-build-docker-dirs PROPERTIES "DOCKER_DEPS" "${DEPS}")

    add_custom_target(celix-build-${DOCKER_TARGET}-docker-image
	    COMMAND cd $<TARGET_PROPERTY:${DOCKER_TARGET},DOCKER_LOC> && ${DOCKER_CMD} build -t "$<TARGET_PROPERTY:${DOCKER_TARGET},DOCKER_IMAGE_NAME>" .
        DEPENDS ${DOCKERFILE} ${DOCKER_TARGET}
        COMMENT "Creating docker image for target '${DOCKER_TARGET}'" VERBATIM
    )
    add_dependencies(celix-build-docker-images celix-build-${DOCKER_TARGET}-docker-image)

endfunction()

function(celix_docker_bundles)
    #0 is docker TARGET
    #1..n is bundles
    list(GET ARGN 0 DOCKER_TARGET)
    list(REMOVE_AT ARGN 0)

    get_target_property(BUNDLES ${DOCKER_TARGET} "DOCKER_BUNDLES")
    get_target_property(BUNDLES_DIR ${DOCKER_TARGET} "DOCKER_BUNDLES_DIR")
    get_target_property(LOC ${DOCKER_TARGET} "DOCKER_LOC")
    get_target_property(DEPS ${DOCKER_TARGET} "DOCKER_DEPS")

    foreach(BUNDLE IN ITEMS ${ARGN})
        if(IS_ABSOLUTE ${BUNDLE} AND EXISTS ${BUNDLE})
            get_filename_component(BUNDLE_FILENAME ${BUNDLE} NAME)
            list(APPEND BUNDLES "${BUNDLES_DIR}/${BUNDLE_FILENAME}")
            set(OUT "${LOC}/${BUNDLES_DIR}/${BUNDLE_FILENAME}")
            add_custom_command(OUTPUT ${OUT}
                COMMAND ${CMAKE_COMMAND} -E copy_if_different ${BUNDLE} ${OUT}
                COMMENT "Copying bundle '${BUNDLE}' to '${OUT}'"
                DEPENDS ${BUNDLE}
            )
        else() #assuming target
            get_target_property(BFN ${BUNDLE} BUNDLE_FILE_NAME)
            list(APPEND BUNDLES "${BUNDLES_DIR}/${BFN}")
            set(OUT ${LOC}/${BUNDLES_DIR}/${BFN})
            add_custom_command(OUTPUT ${OUT}
                    COMMAND ${CMAKE_COMMAND} -E copy_if_different "$<TARGET_PROPERTY:${BUNDLE},BUNDLE_FILE>" "${OUT}"
                    COMMENT "Copying bundle '${BUNDLE}' to '${OUT}'"
                    DEPENDS ${BUNDLE} ${BUNDLE}_bundle
            )
        endif()
        list(APPEND DEPS "${OUT}")
    endforeach()

    set_target_properties(${DOCKER_TARGET} PROPERTIES "DOCKER_BUNDLES" "${BUNDLES}")
    set_target_properties(${DOCKER_TARGET} PROPERTIES "DOCKER_DEPS" "${DEPS}")
endfunction()

function(celix_docker_properties)
    #0 is docker TARGET
    #1..n is properties
    list(GET ARGN 0 DOCKER_TARGET)
    list(REMOVE_AT ARGN 0)

    get_target_property(PROPS ${DOCKER_TARGET} "DOCKER_PROPERTIES")

    foreach(PROP IN ITEMS ${ARGN})
        list(APPEND PROPS ${PROP})
    endforeach()

    set_target_properties(${DOCKER_TARGET} PROPERTIES "DOCKER_PROPERTIES" "${PROPS}")
endfunction()

function(celix_docker_instructions)
    #0 is docker TARGET
    #1..n is instructions
    list(GET ARGN 0 DOCKER_TARGET)
    list(REMOVE_AT ARGN 0)

    get_target_property(INSTRUCTIONS ${DOCKER_TARGET} "DOCKER_INSTRUCTIONS")

    foreach(INSTR IN ITEMS ${ARGN})
        list(APPEND INSTRUCTIONS ${INSTR})
    endforeach()

    set_target_properties(${DOCKER_TARGET} PROPERTIES "DOCKER_INSTRUCTIONS" "${INSTRUCTIONS}")
endfunction()
