# Copyright (C) 2016-2021 HERE Europe B.V.
#
# 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
#
#     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.
#
# SPDX-License-Identifier: Apache-2.0
# License-Filename: LICENSE

if(DEFINED includeguard_gluecodium_AddGenerateCommand)
  return()
endif()
set(includeguard_gluecodium_AddGenerateCommand ON)

cmake_minimum_required(VERSION 3.7)

#[===================================================================================================[.rst:

Adds custom command to generate source code with Gluecodium
-----------------------------------------------------------

.. command:: gluecodium_add_generate_command

The general form of the command is::

  gluecodium_add_generate_command(
    <target>                        # The target to add custom build rules for.
    [GENERATORS <generator> ...]    # The list of source code generators.
                                    # Known value are: cpp, swift, android, dart
                                    # Usually at least two generators needs to be defined
                                    # the one for native code and another for platform,
                                    # for example android;cpp or swift;cpp
                                    # If support of Dart is necessary then it might be specified
                                    # as third generator: android;cpp;dart.
                                    # This options is initialized by the value of the
                                    # `GLUECODIUM_GENERATORS_DEFAULT` variable if it is set when this
                                    # function is called. Of neither `GLUECODIUM_GENERATORS_DEFAULT`
                                    # variable is set nor option `GENERATORS` is specified then
                                    # error is produced.
                                    # Target's read-only property `GLUECODIUM_GENERATORS` is set to
                                    # this value.
    [MAIN_ONLY]                     # Generate only MAIN source set (read below).
                                    # If neither COMMON nor MAIN is specified then both are used.
                                    # Target's read-only property `GLUECODIUM_SOURCE_SETS` is set
                                    # to `MAIN` or list of `COMMON` and `MAIN`.
    [OUTPUT_DIR <path>]             # The path to output directory where source code will be
                                    # generated. The common, main and unity sources are generated by
                                    # path related to the value of this option in their own
                                    # subdirectories like ${OUTPUT_DIR}/common,
                                    # ${OUTPUT_DIR}/main, ${OUTPUT_DIR}/unity
                                    # This options is initialized by the value of the
                                    # `GLUECODIUM_OUTPUT_DIR_DEFAULT` variable if it is set when this
                                    # function is called.
                                    # Default value is set to subfolder of GLUECODIUM_BASE_OUTPUT_DIR
                                    # which is initialised by GLUECODIUM_BASE_OUTPUT_DIR_DEFAULT if
                                    # exists oterwise by ${CMAKE_CURRENT_BINARY_DIR}.
                                    # Also default output folder contains name of all generators
                                    # joined with '-'. This name might be changed with
                                    # variable GLUECODIUM_GENERATOR_FOLDER_DEFAULT
                                    # Target's read-only property `GLUECODIUM_OUTPUT_DIR` is set
                                    # to the computed value.
  )

Lime sources are added with function `gluecodium_target_lime_sources`.

*Source sets*
Generated sources consist of MAIN and COMMON source sets.
The COMMON source set contain the common code which doesn't depend on lime files while MAIN
source set contain the code which is specific for lime files.
If a project contains only one target which uses code generated by Gluecodium then both COMMON and
MAIN source sets must be generated (i.e. option MAIN_ONLY shouldn't be specified).
When a project consist of more than one such targets then dependency should be taken into account.
If the targets are independent on Gluecodium level (i.e. don't have primitives which are declared in lime
file of one target and used in lime file of other target) then both targets still may generate both MAIN
and COMMON source sets.
If the targets use common interfaces/classes then only one of such target should contain COMMON source
set (and MAIN) and other targets must contain only MAIN source set (option MAIN_ONLY must be specified)
and link the target which contains COMMON source set.
It's necessary because COMMON source set contains type registry and it must be instantiated as
singleton only once.

*Configure generated sources*
Gluecodium supports many options to configure generated sources, for example it may include
copyright header, generate sources using specified name rules, use different Nullable/NonNull
annotations in Java, etc. Please refer to file `KnownOptionalProperties.cmake` for full list
of such opotions. All this options might be set to target as property with `set_target_properties`
either before or after usage of this function.
This functions also sets few properties on a target which might be used as read-only properties.
Any attept to change them might lead to undefined behaviour and most probably to configuration
or compilation errors. Examples of such properties are: `GLUECODIUM_GENERATORS`,
`GLUECODIUM_OUTPUT_DIR`, `GLUECODIUM_SOURCE_SETS`.

#]===================================================================================================]

# i.e. gluecodium/gluecodium directory
set(GLUECODIUM_GLUECODIUM_DIR ${CMAKE_CURRENT_LIST_DIR})
set(GLUECODIUM_DETAILS_DIR ${GLUECODIUM_GLUECODIUM_DIR}/details)

include(${GLUECODIUM_GLUECODIUM_DIR}/KnownOptionalProperties.cmake)

include(${GLUECODIUM_DETAILS_DIR}/StringJoin.cmake)
include(${GLUECODIUM_DETAILS_DIR}/CheckArguments.cmake)
include(${GLUECODIUM_DETAILS_DIR}/AppendTargetProperty.cmake)
include(${GLUECODIUM_DETAILS_DIR}/ListGeneratedFiles.cmake)
include(${GLUECODIUM_DETAILS_DIR}/AppendTargetProperty.cmake)

function(gluecodium_add_generate_command _target)
  set(_options MAIN_ONLY)
  set(_single_args OUTPUT_DIR)
  set(_multi_args GENERATORS)

  cmake_parse_arguments(_args "${_options}" "${_single_args}" "${_multi_args}" ${ARGN})

  gluecodium_check_no_unparsed_arguments(_args gluecodium_add_generate_command)

  if(NOT _args_GENERATORS)
    _gluecodium_get_default_value_for_variable(_args_GENERATORS GLUECODIUM_GENERATORS)
  endif()

  gluecodium_require_argument(_args GENERATORS gluecodium_add_generate_command)
  list(SORT _args_GENERATORS)

  set_property(TARGET ${_target} APPEND PROPERTY GLUECODIUM_GENERATORS ${_args_GENERATORS})

  _gluecodium_construct_output_folder(_args_OUTPUT_DIR ${_target} ${_args_GENERATORS})

  set(_source_sets MAIN)
  if(NOT _args_MAIN_ONLY)
    list(APPEND _source_sets COMMON)
  endif()

  set_target_properties(
    ${_target} PROPERTIES GLUECODIUM_OUTPUT_DIR "${_args_OUTPUT_DIR}" GLUECODIUM_SOURCE_SETS
                                                                      "${_source_sets}")

  gluecodium_list_generated_files(${_target} OUTPUT_ALL _generated_files)

  set(_options_file "${_args_OUTPUT_DIR}/gluecodium-generate-options-${_target}.txt")
  if(NOT IS_ABSOLUTE "${_options_file}")
    set(_options_file "${CMAKE_CURRENT_BINARY_DIR}/${_options_file}")
  endif()
  list(APPEND _generated_files "${_options_file}")

  unset(_configuration_content)
  string(APPEND _configuration_content "set(GLUECODIUM_TARGET_NAME \"${_target}\")\n")
  string(APPEND _configuration_content "set(GLUECODIUM_GENERATORS \"${_args_GENERATORS}\")\n")
  string(APPEND _configuration_content "set(GLUECODIUM_SOURCE_SETS \"${_source_sets}\")\n")
  string(APPEND _configuration_content "set(GLUECODIUM_OUTPUT_DIR \"${_args_OUTPUT_DIR}\")\n")
  string(APPEND _configuration_content
         "set(GLUECODIUM_DETAILS_DIR \"${GLUECODIUM_DETAILS_DIR}\")\n")
  string(APPEND _configuration_content
         "set(GLUECODIUM_GRADLE_SYNCHRONISATION_DIR \"${CMAKE_BINARY_DIR}\")\n")
  string(APPEND _configuration_content
         "set(GLUECODIUM_TARGET_SOURCE_DIR \"${CMAKE_CURRENT_SOURCE_DIR}\")\n")
  string(APPEND _configuration_content
         "set(GLUECODIUM_TARGET_BINARY_DIR \"${CMAKE_CURRENT_BINARY_DIR}\")\n")
  string(APPEND _configuration_content "set(GLUECODIUM_OPTIONS_FILE \"${_options_file}\")\n")

  get_property(_known_optional_properties GLOBAL PROPERTY GLUECODIUM_KNOWN_OPTIONAL_PROPERTIES)

  foreach(_known_property ${_known_optional_properties})
    _gluecodium_set_property_if_globally_initialised(${_target} ${_known_property})
    _gluecodium_append_target_property_eval(_configuration_content ${_target} ${_known_property})
  endforeach()

  # SOURCES property contains also public sources from dependencies.
  set(_target_sources "$<TARGET_PROPERTY:${_target},SOURCES>")
  set(_target_interface_sources "$<TARGET_PROPERTY:${_target},INTERFACE_SOURCES>")
  set(_target_lime_sources "$<TARGET_PROPERTY:${_target},GLUECODIUM_LIME_SOURCES>")

  if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.15 AND NOT
                                                  GLUECODIUM_DONT_USE_FILTER_GENERATOR_EXPRESSION)
    set(_lime_target_primary_lime_sources "$<FILTER:${_target_lime_sources},INCLUDE,.*\\.lime$>")
    set(_lime_target_primary_lime_sources
        "$<REMOVE_DUPLICATES:${_lime_target_primary_lime_sources}>")
    string(APPEND _configuration_content
           "set(GLUECODIUM_LIME_SOURCES \"${_lime_target_primary_lime_sources}\")\n")

    set(_lime_target_all_lime_sources
        "$<FILTER:${_target_lime_sources}$<SEMICOLON>${_target_sources}$<SEMICOLON>${_target_interface_sources},INCLUDE,.*\\.lime$>"
    )
    set(_lime_target_all_lime_sources "$<REMOVE_DUPLICATES:${_lime_target_all_lime_sources}>")
    string(APPEND _configuration_content
           "set(GLUECODIUM_LIME_SOURCES_AUX \"${_lime_target_all_lime_sources}\")\n")
    set(_command_dependencies ${_lime_target_all_lime_sources})
  else()
    # There is no $<FILTER: ... > in old CMake, so it's not possible to filter out other sources so
    # all lime sources mixed with C++ and other sources must be written to config file and filtered
    # afterwards. The biggest disadvantage is that custom commands depends on ALL public sources
    # from dependencies.
    string(APPEND _configuration_content "set(GLUECODIUM_LIME_SOURCES_REQUIRE_FILTER ON)\n")
    set(_lime_target_all_lime_sources
        "${_target_lime_sources}$<SEMICOLON>${_target_sources}$<SEMICOLON>${_target_interface_sources}"
    )

    string(APPEND _configuration_content
           "set(GLUECODIUM_LIME_SOURCES \"${_target_lime_sources}\")\n")
    string(APPEND _configuration_content
           "set(GLUECODIUM_LIME_SOURCES_AUX \"${_lime_target_all_lime_sources}\")\n")
    set(_command_dependencies $<TARGET_PROPERTY:${_target},LINK_LIBRARIES>)
  endif()

  # Files with CMake options must not be in folder with generated sources to be able to remove
  # generated sources.
  _gluecodium_get_default_generator_folder(_default_generator_folder ${_args_GENERATORS})
  set(_gluecodium_configuration_file
      "${CMAKE_CURRENT_BINARY_DIR}/gluecodium/${_target}/${_default_generator_folder}/gluecodium-generate-configuration-${_target}.cmake"
  )
  file(GENERATE OUTPUT "${_gluecodium_configuration_file}" CONTENT "${_configuration_content}")
  list(APPEND _command_dependencies "${_gluecodium_configuration_file}")

  add_custom_command(
    OUTPUT ${_generated_files}
    COMMAND ${CMAKE_COMMAND} -DGLUECODIUM_CONFIGURATION_FILE=${_gluecodium_configuration_file} -P
            ${GLUECODIUM_DETAILS_DIR}/runGenerate.cmake
    VERBATIM
    DEPENDS "${GLUECODIUM_DETAILS_DIR}/runGenerate.cmake" ${_command_dependencies})
endfunction()

function(_gluecodium_get_default_generator_folder result)
  string_join("-" _default_generator_folder ${ARGN})
  set(${result} ${_default_generator_folder} PARENT_SCOPE)
endfunction()

function(_gluecodium_construct_output_folder result _target)
  if(${result})
    return()
  endif()

  unset(_output_dir)
  _gluecodium_get_default_value_for_variable(_output_dir GLUECODIUM_OUTPUT_DIR)
  if(_output_dir)
    set(${result} "${_output_dir}" PARENT_SCOPE)
    return()
  endif()

  unset(_generator_folder)
  _gluecodium_get_default_value_for_variable(_generator_folder GLUECODIUM_GENERATOR_FOLDER)
  if(NOT _generator_folder)
    _gluecodium_get_default_generator_folder(_generator_folder ${ARGN})
  endif()

  unset(_base_output_dir)
  _gluecodium_get_default_value_for_variable(_base_output_dir GLUECODIUM_BASE_OUTPUT_DIR)
  if(NOT _base_output_dir)
    set(_base_output_dir "${CMAKE_CURRENT_BINARY_DIR}/gluecodium")
  endif()

  set(${result} "${_base_output_dir}/${_target}/${_generator_folder}" PARENT_SCOPE)
endfunction()
